/*
 * Copyright (C) 2017 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "classic.h"
#include "classic.c"

#include <glib.h>

/* restore_os_release is an internal helper for mock_os_release */
static void restore_os_release(gpointer *old)
{
	unlink(os_release);
	os_release = (const char *)old;
}

/* mock_os_release replaces the presence and contents of /etc/os-release
   as seen by classic.c. The mocked value may be NULL to have the code refer
   to an absent file. */
static void mock_os_release(const char *mocked)
{
	const char *old = os_release;
	if (mocked != NULL) {
		os_release = "os-release.test";
		g_assert_true(g_file_set_contents
			      (os_release, mocked, -1, NULL));
	} else {
		os_release = "os-release.missing";
	}
	g_test_queue_destroy((GDestroyNotify) restore_os_release,
			     (gpointer) old);
}

/* restore_meta_snap_yaml is an internal helper for mock_meta_snap_yaml */
static void restore_meta_snap_yaml(gpointer *old)
{
	unlink(meta_snap_yaml);
	meta_snap_yaml = (const char *)old;
}

/* mock_meta_snap_yaml replaces the presence and contents of /meta/snap.yaml
   as seen by classic.c. The mocked value may be NULL to have the code refer
   to an absent file. */
static void mock_meta_snap_yaml(const char *mocked)
{
	const char *old = meta_snap_yaml;
	if (mocked != NULL) {
		meta_snap_yaml = "snap-yaml.test";
		g_assert_true(g_file_set_contents
			      (meta_snap_yaml, mocked, -1, NULL));
	} else {
		meta_snap_yaml = "snap-yaml.missing";
	}
	g_test_queue_destroy((GDestroyNotify) restore_meta_snap_yaml,
			     (gpointer) old);
}

static const char *os_release_classic = ""
    "NAME=\"Ubuntu\"\n"
    "VERSION=\"17.04 (Zesty Zapus)\"\n" "ID=ubuntu\n" "ID_LIKE=debian\n";

static void test_is_on_classic(void)
{
	mock_os_release(os_release_classic);
	mock_meta_snap_yaml(NULL);
	g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CLASSIC);
}

static const char *os_release_core16 = ""
    "NAME=\"Ubuntu Core\"\n" "VERSION_ID=\"16\"\n" "ID=ubuntu-core\n";

static const char *meta_snap_yaml_core16 = ""
    "name: core\n"
    "version: 16-something\n" "type: core\n" "architectures: [amd64]\n";

static void test_is_on_core_on16(void)
{
	mock_os_release(os_release_core16);
	mock_meta_snap_yaml(meta_snap_yaml_core16);
	g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE16);
}

static const char *os_release_core18 = ""
    "NAME=\"Ubuntu Core\"\n" "VERSION_ID=\"18\"\n" "ID=ubuntu-core\n";

static const char *meta_snap_yaml_core18 = ""
    "name: core18\n" "type: base\n" "architectures: [amd64]\n";

static void test_is_on_core_on18(void)
{
	mock_os_release(os_release_core18);
	mock_meta_snap_yaml(meta_snap_yaml_core18);
	g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER);
}

const char *os_release_core20 = ""
    "NAME=\"Ubuntu Core\"\n" "VERSION_ID=\"20\"\n" "ID=ubuntu-core\n";

static const char *meta_snap_yaml_core20 = ""
    "name: core20\n" "type: base\n" "architectures: [amd64]\n";

static void test_is_on_core_on20(void)
{
	mock_os_release(os_release_core20);
	mock_meta_snap_yaml(meta_snap_yaml_core20);
	g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER);
}

static const char *os_release_classic_with_long_line = ""
    "NAME=\"Ubuntu\"\n"
    "VERSION=\"17.04 (Zesty Zapus)\"\n"
    "ID=ubuntu\n"
    "ID_LIKE=debian\n"
    "LONG=line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.";

static void test_is_on_classic_with_long_line(void)
{
	mock_os_release(os_release_classic_with_long_line);
	mock_meta_snap_yaml(NULL);
	g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CLASSIC);
}

static const char *os_release_fedora_base = ""
    "NAME=Fedora\nID=fedora\nVARIANT_ID=snappy\n";

static const char *meta_snap_yaml_fedora_base = ""
    "name: fedora29\n" "type: base\n" "architectures: [amd64]\n";

static void test_is_on_fedora_base(void)
{
	mock_os_release(os_release_fedora_base);
	mock_meta_snap_yaml(meta_snap_yaml_fedora_base);
	g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER);
}

static const char *os_release_fedora_ws = ""
    "NAME=Fedora\nID=fedora\nVARIANT_ID=workstation\n";

static void test_is_on_fedora_ws(void)
{
	mock_os_release(os_release_fedora_ws);
	mock_meta_snap_yaml(NULL);
	g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CLASSIC);
}

static const char *os_release_custom = ""
    "NAME=\"Custom Distribution\"\nID=custom\n";

static const char *meta_snap_yaml_custom = ""
    "name: custom\n"
    "version: rolling\n"
    "summary: Runtime environment based on Custom Distribution\n"
    "type: base\n" "architectures: [amd64]\n";

static void test_is_on_custom_base(void)
{
	mock_os_release(os_release_custom);

	/* Without /meta/snap.yaml we treat "Custom Distribution" as classic. */
	mock_meta_snap_yaml(NULL);
	g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CLASSIC);

	/* With /meta/snap.yaml we treat it as core instead. */
	mock_meta_snap_yaml(meta_snap_yaml_custom);
	g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER);
}

static const char *os_release_debian_like_valid = ""
    "ID=my-fun-distro\n" "ID_LIKE=debian\n";

static const char *os_release_debian_like_quoted_valid = ""
    "ID=my-fun-distro\n" "ID_LIKE=\"debian\"\n";

/* actual debian only sets ID=debian */
static const char *os_release_actual_debian_valid = "ID=debian\n";

static const char *os_release_invalid = "garbage\n";

static void test_is_debian_like(void)
{
	mock_os_release(os_release_debian_like_valid);
	g_assert_true(sc_is_debian_like());

	mock_os_release(os_release_debian_like_quoted_valid);
	g_assert_true(sc_is_debian_like());

	mock_os_release(os_release_actual_debian_valid);
	g_assert_true(sc_is_debian_like());

	mock_os_release(os_release_fedora_ws);
	g_assert_false(sc_is_debian_like());

	mock_os_release(os_release_invalid);
	g_assert_false(sc_is_debian_like());
}

static void __attribute__((constructor)) init(void)
{
	g_test_add_func("/classic/on-classic", test_is_on_classic);
	g_test_add_func("/classic/on-classic-with-long-line",
			test_is_on_classic_with_long_line);
	g_test_add_func("/classic/on-core-on16", test_is_on_core_on16);
	g_test_add_func("/classic/on-core-on18", test_is_on_core_on18);
	g_test_add_func("/classic/on-core-on20", test_is_on_core_on20);
	g_test_add_func("/classic/on-fedora-base", test_is_on_fedora_base);
	g_test_add_func("/classic/on-fedora-ws", test_is_on_fedora_ws);
	g_test_add_func("/classic/on-custom-base", test_is_on_custom_base);
	g_test_add_func("/classic/is-debian-like", test_is_debian_like);
}
