private class SysfsFixture : Object {
    public string root;

    public SysfsFixture() throws Error {
        root = DirUtils.make_tmp("quick-setup-sysfs-test-XXXXXX");
        File.new_for_path(Path.build_filename(root, "class", "drm"))
            .make_directory_with_parents();
    }

    public void add_card(
        int card_number,
        string driver_name,
        bool relative_link = false
    ) throws Error {
        string driver_path = Path.build_filename(
            root,
            "bus",
            "pci",
            "drivers",
            driver_name
        );
        if (!FileUtils.test(driver_path, FileTest.IS_DIR)) {
            File.new_for_path(driver_path).make_directory_with_parents();
        }

        string device_path = Path.build_filename(
            root,
            "class",
            "drm",
            "card%d".printf(card_number),
            "device"
        );
        File.new_for_path(device_path).make_directory_with_parents();
        string link_target = driver_path;
        if (relative_link) {
            link_target = Path.build_filename(
                "..", "..", "..", "..", "bus", "pci", "drivers", driver_name
            );
        }

        File.new_for_path(Path.build_filename(device_path, "driver"))
            .make_symbolic_link(link_target, null);
    }

    public void add_broken_card(int card_number, string driver_name) throws Error {
        string device_path = Path.build_filename(
            root,
            "class",
            "drm",
            "card%d".printf(card_number),
            "device"
        );
        File.new_for_path(device_path).make_directory_with_parents();
        File.new_for_path(Path.build_filename(device_path, "driver"))
            .make_symbolic_link(
                Path.build_filename(root, "missing", "drivers", driver_name),
                null
            );
    }

    public void add_non_card_entry(string entry_name, string driver_name) throws Error {
        string driver_path = Path.build_filename(
            root,
            "bus",
            "pci",
            "drivers",
            driver_name
        );
        if (!FileUtils.test(driver_path, FileTest.IS_DIR)) {
            File.new_for_path(driver_path).make_directory_with_parents();
        }

        string device_path = Path.build_filename(
            root,
            "class",
            "drm",
            entry_name,
            "device"
        );
        File.new_for_path(device_path).make_directory_with_parents();
        File.new_for_path(Path.build_filename(device_path, "driver"))
            .make_symbolic_link(driver_path, null);
    }

    ~SysfsFixture() {
        remove_tree(File.new_for_path(root));
    }

    private static void remove_tree(File file) {
        try {
            if (file.query_file_type(FileQueryInfoFlags.NOFOLLOW_SYMLINKS)
                    == FileType.DIRECTORY) {
                var children = file.enumerate_children(
                    FileAttribute.STANDARD_NAME,
                    FileQueryInfoFlags.NOFOLLOW_SYMLINKS
                );
                FileInfo? child_info;

                while ((child_info = children.next_file()) != null) {
                    remove_tree(file.get_child(child_info.get_name()));
                }
            }

            file.delete();
        } catch (Error error) {
            // Test cleanup is best effort.
        }
    }
}

private void test_nouveau_only() {
    try {
        var fixture = new SysfsFixture();
        fixture.add_card(0, "nouveau");
        assert(QuickSetup.is_nouveau_active(fixture.root));
    } catch (Error error) {
        Test.fail_printf("Failed to create sysfs fixture: %s", error.message);
    }
}

private void test_relative_driver_link() {
    try {
        var fixture = new SysfsFixture();
        fixture.add_card(0, "nouveau", true);
        assert(QuickSetup.is_nouveau_active(fixture.root));
    } catch (Error error) {
        Test.fail_printf("Failed to create sysfs fixture: %s", error.message);
    }
}

private void test_intel_and_amd() {
    try {
        var fixture = new SysfsFixture();
        fixture.add_card(0, "i915");
        fixture.add_card(1, "amdgpu");
        assert(!QuickSetup.is_nouveau_active(fixture.root));
    } catch (Error error) {
        Test.fail_printf("Failed to create sysfs fixture: %s", error.message);
    }
}

private void test_hybrid_intel_and_nouveau() {
    try {
        var fixture = new SysfsFixture();
        fixture.add_card(0, "i915");
        fixture.add_card(1, "nouveau");
        assert(QuickSetup.is_nouveau_active(fixture.root));
    } catch (Error error) {
        Test.fail_printf("Failed to create sysfs fixture: %s", error.message);
    }
}

private void test_proprietary_nvidia() {
    try {
        var fixture = new SysfsFixture();
        fixture.add_card(0, "nvidia");
        assert(!QuickSetup.is_nouveau_active(fixture.root));
    } catch (Error error) {
        Test.fail_printf("Failed to create sysfs fixture: %s", error.message);
    }
}

private void test_empty_drm_directory() {
    try {
        var fixture = new SysfsFixture();
        assert(!QuickSetup.is_nouveau_active(fixture.root));
    } catch (Error error) {
        Test.fail_printf("Failed to create sysfs fixture: %s", error.message);
    }
}

private void test_broken_nouveau_link() {
    try {
        var fixture = new SysfsFixture();
        fixture.add_broken_card(0, "nouveau");
        assert(!QuickSetup.is_nouveau_active(fixture.root));
    } catch (Error error) {
        Test.fail_printf("Failed to create sysfs fixture: %s", error.message);
    }
}

private void test_non_card_entries_are_ignored() {
    try {
        var fixture = new SysfsFixture();
        fixture.add_non_card_entry("card0-DP-1", "nouveau");
        fixture.add_non_card_entry("renderD128", "nouveau");
        fixture.add_non_card_entry("card", "nouveau");
        assert(!QuickSetup.is_nouveau_active(fixture.root));
    } catch (Error error) {
        Test.fail_printf("Failed to create sysfs fixture: %s", error.message);
    }
}

private void test_missing_or_unreadable_sysfs() {
    assert(!QuickSetup.is_nouveau_active("/path/that/does/not/exist"));

    try {
        string root = DirUtils.make_tmp("quick-setup-sysfs-test-XXXXXX");
        File.new_for_path(Path.build_filename(root, "class"))
            .make_directory_with_parents();
        FileUtils.set_contents(Path.build_filename(root, "class", "drm"), "not a directory");
        assert(!QuickSetup.is_nouveau_active(root));

        File.new_for_path(Path.build_filename(root, "class", "drm")).delete();
        File.new_for_path(Path.build_filename(root, "class")).delete();
        File.new_for_path(root).delete();
    } catch (Error error) {
        Test.fail_printf("Failed to create unreadable sysfs fixture: %s", error.message);
    }
}

private void test_existing_renderer_is_preserved() {
    string? previous_renderer = Environment.get_variable("GSK_RENDERER");

    try {
        var fixture = new SysfsFixture();
        fixture.add_card(0, "nouveau");
        Environment.set_variable("GSK_RENDERER", "vulkan", true);

        assert(!QuickSetup.enable_nouveau_renderer_workaround(fixture.root));
        assert(Environment.get_variable("GSK_RENDERER") == "vulkan");
    } catch (Error error) {
        Test.fail_printf("Failed to create sysfs fixture: %s", error.message);
    } finally {
        if (previous_renderer == null) {
            Environment.unset_variable("GSK_RENDERER");
        } else {
            Environment.set_variable("GSK_RENDERER", previous_renderer, true);
        }
    }
}

private void test_empty_renderer_is_preserved() {
    string? previous_renderer = Environment.get_variable("GSK_RENDERER");

    try {
        var fixture = new SysfsFixture();
        fixture.add_card(0, "nouveau");
        Environment.set_variable("GSK_RENDERER", "", true);

        assert(!QuickSetup.enable_nouveau_renderer_workaround(fixture.root));
        assert(Environment.get_variable("GSK_RENDERER") == "");
    } catch (Error error) {
        Test.fail_printf("Failed to create sysfs fixture: %s", error.message);
    } finally {
        if (previous_renderer == null) {
            Environment.unset_variable("GSK_RENDERER");
        } else {
            Environment.set_variable("GSK_RENDERER", previous_renderer, true);
        }
    }
}

private void test_renderer_unchanged_without_nouveau() {
    string? previous_renderer = Environment.get_variable("GSK_RENDERER");

    try {
        var fixture = new SysfsFixture();
        fixture.add_card(0, "i915");
        Environment.unset_variable("GSK_RENDERER");

        assert(!QuickSetup.enable_nouveau_renderer_workaround(fixture.root));
        assert(Environment.get_variable("GSK_RENDERER") == null);
    } catch (Error error) {
        Test.fail_printf("Failed to create sysfs fixture: %s", error.message);
    } finally {
        if (previous_renderer == null) {
            Environment.unset_variable("GSK_RENDERER");
        } else {
            Environment.set_variable("GSK_RENDERER", previous_renderer, true);
        }
    }
}

private void test_renderer_is_set_for_nouveau() {
    string? previous_renderer = Environment.get_variable("GSK_RENDERER");

    try {
        var fixture = new SysfsFixture();
        fixture.add_card(0, "nouveau");
        Environment.unset_variable("GSK_RENDERER");

        assert(QuickSetup.enable_nouveau_renderer_workaround(fixture.root));
        assert(Environment.get_variable("GSK_RENDERER") == "gl");
    } catch (Error error) {
        Test.fail_printf("Failed to create sysfs fixture: %s", error.message);
    } finally {
        if (previous_renderer == null) {
            Environment.unset_variable("GSK_RENDERER");
        } else {
            Environment.set_variable("GSK_RENDERER", previous_renderer, true);
        }
    }
}

public int main(string[] args) {
    Test.init(ref args);
    Test.add_func("/nouveau-workaround/nouveau-only", test_nouveau_only);
    Test.add_func("/nouveau-workaround/relative-driver-link", test_relative_driver_link);
    Test.add_func("/nouveau-workaround/intel-and-amd", test_intel_and_amd);
    Test.add_func("/nouveau-workaround/hybrid-intel-nouveau", test_hybrid_intel_and_nouveau);
    Test.add_func("/nouveau-workaround/proprietary-nvidia", test_proprietary_nvidia);
    Test.add_func("/nouveau-workaround/empty-drm-directory", test_empty_drm_directory);
    Test.add_func("/nouveau-workaround/broken-nouveau-link", test_broken_nouveau_link);
    Test.add_func("/nouveau-workaround/non-card-entries", test_non_card_entries_are_ignored);
    Test.add_func("/nouveau-workaround/missing-or-unreadable-sysfs", test_missing_or_unreadable_sysfs);
    Test.add_func("/nouveau-workaround/existing-renderer-preserved", test_existing_renderer_is_preserved);
    Test.add_func("/nouveau-workaround/empty-renderer-preserved", test_empty_renderer_is_preserved);
    Test.add_func("/nouveau-workaround/renderer-unchanged-without-nouveau", test_renderer_unchanged_without_nouveau);
    Test.add_func("/nouveau-workaround/renderer-set-for-nouveau", test_renderer_is_set_for_nouveau);
    return Test.run();
}
