// Copyright 2024 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package fsgen

import (
	"android/soong/android"
	"android/soong/etc"
	"android/soong/filesystem"
	"android/soong/java"
	"testing"

	"github.com/google/blueprint/proptools"
)

var prepareForTestWithFsgenBuildComponents = android.FixtureRegisterWithContext(registerBuildComponents)

func TestFileSystemCreatorSystemImageProps(t *testing.T) {
	result := android.GroupFixturePreparers(
		android.PrepareForIntegrationTestWithAndroid,
		android.PrepareForTestWithAndroidBuildComponents,
		android.PrepareForTestWithAllowMissingDependencies,
		filesystem.PrepareForTestWithFilesystemBuildComponents,
		prepareForTestWithFsgenBuildComponents,
		android.FixtureModifyConfig(func(config android.Config) {
			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.BoardAvbEnable = true
			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.PartitionQualifiedVariables =
				map[string]android.PartitionQualifiedVariablesType{
					"system": {
						BoardAvbKeyPath:       "external/avb/test/data/testkey_rsa4096.pem",
						BoardAvbAlgorithm:     "SHA256_RSA4096",
						BoardAvbRollbackIndex: "0",
						BoardFileSystemType:   "ext4",
					},
				}
		}),
		android.FixtureMergeMockFs(android.MockFS{
			"external/avb/test/data/testkey_rsa4096.pem": nil,
			"external/avb/test/Android.bp": []byte(`
			filegroup {
				name: "avb_testkey_rsa4096",
				srcs: ["data/testkey_rsa4096.pem"],
			}
			`),
			"build/soong/fsgen/Android.bp": []byte(`
			soong_filesystem_creator {
				name: "foo",
			}
			`),
		}),
	).RunTest(t)

	fooSystem := result.ModuleForTests("test_product_generated_system_image", "android_common").Module().(interface {
		FsProps() filesystem.FilesystemProperties
	})
	android.AssertBoolEquals(
		t,
		"Property expected to match the product variable 'BOARD_AVB_ENABLE'",
		true,
		proptools.Bool(fooSystem.FsProps().Use_avb),
	)
	android.AssertStringEquals(
		t,
		"Property the avb_private_key property to be set to the existing filegroup",
		":avb_testkey_rsa4096",
		proptools.String(fooSystem.FsProps().Avb_private_key),
	)
	android.AssertStringEquals(
		t,
		"Property expected to match the product variable 'BOARD_AVB_ALGORITHM'",
		"SHA256_RSA4096",
		proptools.String(fooSystem.FsProps().Avb_algorithm),
	)
	android.AssertIntEquals(
		t,
		"Property expected to match the product variable 'BOARD_AVB_SYSTEM_ROLLBACK_INDEX'",
		0,
		proptools.Int(fooSystem.FsProps().Rollback_index),
	)
	android.AssertStringEquals(
		t,
		"Property expected to match the product variable 'BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE'",
		"ext4",
		proptools.String(fooSystem.FsProps().Type),
	)
}

func TestFileSystemCreatorSetPartitionDeps(t *testing.T) {
	result := android.GroupFixturePreparers(
		android.PrepareForIntegrationTestWithAndroid,
		android.PrepareForTestWithAndroidBuildComponents,
		android.PrepareForTestWithAllowMissingDependencies,
		filesystem.PrepareForTestWithFilesystemBuildComponents,
		prepareForTestWithFsgenBuildComponents,
		java.PrepareForTestWithJavaBuildComponents,
		java.PrepareForTestWithJavaDefaultModules,
		android.FixtureModifyConfig(func(config android.Config) {
			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.ProductPackages = []string{"bar", "baz"}
			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.PartitionQualifiedVariables =
				map[string]android.PartitionQualifiedVariablesType{
					"system": {
						BoardFileSystemType: "ext4",
					},
				}
		}),
		android.FixtureMergeMockFs(android.MockFS{
			"external/avb/test/data/testkey_rsa4096.pem": nil,
			"build/soong/fsgen/Android.bp": []byte(`
			soong_filesystem_creator {
				name: "foo",
			}
			`),
		}),
	).RunTestWithBp(t, `
	java_library {
		name: "bar",
		srcs: ["A.java"],
	}
	java_library {
		name: "baz",
		srcs: ["A.java"],
		product_specific: true,
	}
	`)

	android.AssertBoolEquals(
		t,
		"Generated system image expected to depend on system partition installed \"bar\"",
		true,
		java.CheckModuleHasDependency(t, result.TestContext, "test_product_generated_system_image", "android_common", "bar"),
	)
	android.AssertBoolEquals(
		t,
		"Generated system image expected to not depend on product partition installed \"baz\"",
		false,
		java.CheckModuleHasDependency(t, result.TestContext, "test_product_generated_system_image", "android_common", "baz"),
	)
}

func TestFileSystemCreatorDepsWithNamespace(t *testing.T) {
	result := android.GroupFixturePreparers(
		android.PrepareForIntegrationTestWithAndroid,
		android.PrepareForTestWithAndroidBuildComponents,
		android.PrepareForTestWithAllowMissingDependencies,
		android.PrepareForTestWithNamespace,
		android.PrepareForTestWithArchMutator,
		filesystem.PrepareForTestWithFilesystemBuildComponents,
		prepareForTestWithFsgenBuildComponents,
		java.PrepareForTestWithJavaBuildComponents,
		java.PrepareForTestWithJavaDefaultModules,
		android.FixtureModifyConfig(func(config android.Config) {
			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.ProductPackages = []string{"bar"}
			config.TestProductVariables.NamespacesToExport = []string{"a/b"}
			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.PartitionQualifiedVariables =
				map[string]android.PartitionQualifiedVariablesType{
					"system": {
						BoardFileSystemType: "ext4",
					},
				}
		}),
		android.PrepareForNativeBridgeEnabled,
		android.FixtureMergeMockFs(android.MockFS{
			"external/avb/test/data/testkey_rsa4096.pem": nil,
			"build/soong/fsgen/Android.bp": []byte(`
			soong_filesystem_creator {
				name: "foo",
			}
			`),
			"a/b/Android.bp": []byte(`
			soong_namespace{
			}
			java_library {
				name: "bar",
				srcs: ["A.java"],
				compile_multilib: "64",
			}
			`),
			"c/d/Android.bp": []byte(`
			soong_namespace{
			}
			java_library {
				name: "bar",
				srcs: ["A.java"],
			}
			`),
		}),
	).RunTest(t)

	var packagingProps android.PackagingProperties
	for _, prop := range result.ModuleForTests("test_product_generated_system_image", "android_common").Module().GetProperties() {
		if packagingPropStruct, ok := prop.(*android.PackagingProperties); ok {
			packagingProps = *packagingPropStruct
		}
	}
	moduleDeps := packagingProps.Multilib.Lib64.Deps

	eval := result.ModuleForTests("test_product_generated_system_image", "android_common").Module().ConfigurableEvaluator(android.PanickingConfigAndErrorContext(result.TestContext))
	android.AssertStringListContains(
		t,
		"Generated system image expected to depend on \"bar\" defined in \"a/b\" namespace",
		moduleDeps.GetOrDefault(eval, nil),
		"//a/b:bar",
	)
	android.AssertStringListDoesNotContain(
		t,
		"Generated system image expected to not depend on \"bar\" defined in \"c/d\" namespace",
		moduleDeps.GetOrDefault(eval, nil),
		"//c/d:bar",
	)
}

func TestRemoveOverriddenModulesFromDeps(t *testing.T) {
	result := android.GroupFixturePreparers(
		android.PrepareForIntegrationTestWithAndroid,
		android.PrepareForTestWithAndroidBuildComponents,
		android.PrepareForTestWithAllowMissingDependencies,
		prepareForTestWithFsgenBuildComponents,
		java.PrepareForTestWithJavaBuildComponents,
		android.FixtureMergeMockFs(android.MockFS{
			"external/avb/test/data/testkey_rsa4096.pem": nil,
			"build/soong/fsgen/Android.bp": []byte(`
			soong_filesystem_creator {
				name: "foo",
			}
			`),
		}),
		android.FixtureModifyConfig(func(config android.Config) {
			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.ProductPackages = []string{"libfoo", "libbar"}
		}),
	).RunTestWithBp(t, `
java_library {
	name: "libfoo",
}
java_library {
	name: "libbar",
	required: ["libbaz"],
}
java_library {
	name: "libbaz",
	overrides: ["libfoo"], // overrides libfoo
}
	`)
	resolvedSystemDeps := result.TestContext.Config().Get(fsGenStateOnceKey).(*FsGenState).fsDeps["system"]
	_, libFooInDeps := (*resolvedSystemDeps)["libfoo"]
	android.AssertBoolEquals(t, "libfoo should not appear in deps because it has been overridden by libbaz. The latter is a required dep of libbar, which is listed in PRODUCT_PACKAGES", false, libFooInDeps)
}

func TestPrebuiltEtcModuleGen(t *testing.T) {
	result := android.GroupFixturePreparers(
		android.PrepareForIntegrationTestWithAndroid,
		android.PrepareForTestWithAndroidBuildComponents,
		android.PrepareForTestWithAllowMissingDependencies,
		filesystem.PrepareForTestWithFilesystemBuildComponents,
		prepareForTestWithFsgenBuildComponents,
		android.FixtureModifyConfig(func(config android.Config) {
			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.ProductCopyFiles = []string{
				"frameworks/base/config/preloaded-classes:system/etc/preloaded-classes",
				"frameworks/base/data/keyboards/Vendor_0079_Product_0011.kl:system/usr/keylayout/subdir/Vendor_0079_Product_0011.kl",
				"frameworks/base/data/keyboards/Vendor_0079_Product_18d4.kl:system/usr/keylayout/subdir/Vendor_0079_Product_18d4.kl",
				"some/non/existing/file.txt:system/etc/file.txt",
				"device/sample/etc/apns-full-conf.xml:product/etc/apns-conf.xml:google",
				"device/sample/etc/apns-full-conf.xml:product/etc/apns-conf-2.xml",
				"device/sample/etc/apns-full-conf.xml:system/foo/file.txt",
				"device/sample/etc/apns-full-conf.xml:system/foo/apns-full-conf.xml",
			}
			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.PartitionQualifiedVariables =
				map[string]android.PartitionQualifiedVariablesType{
					"system": {
						BoardFileSystemType: "ext4",
					},
				}
		}),
		android.FixtureMergeMockFs(android.MockFS{
			"external/avb/test/data/testkey_rsa4096.pem": nil,
			"build/soong/fsgen/Android.bp": []byte(`
			soong_filesystem_creator {
				name: "foo",
			}
			`),
			"frameworks/base/config/preloaded-classes":                   nil,
			"frameworks/base/data/keyboards/Vendor_0079_Product_0011.kl": nil,
			"frameworks/base/data/keyboards/Vendor_0079_Product_18d4.kl": nil,
			"device/sample/etc/apns-full-conf.xml":                       nil,
		}),
	).RunTest(t)

	checkModuleProp := func(m android.Module, matcher func(actual interface{}) bool) bool {
		for _, prop := range m.GetProperties() {

			if matcher(prop) {
				return true
			}
		}
		return false
	}

	// check generated prebuilt_* module type install path and install partition
	generatedModule := result.ModuleForTests("system-frameworks_base_config-etc-0", "android_arm64_armv8-a").Module()
	etcModule, _ := generatedModule.(*etc.PrebuiltEtc)
	android.AssertStringEquals(
		t,
		"module expected to have etc install path",
		"etc",
		etcModule.BaseDir(),
	)
	android.AssertBoolEquals(
		t,
		"module expected to be installed in system partition",
		true,
		!generatedModule.InstallInProduct() &&
			!generatedModule.InstallInVendor() &&
			!generatedModule.InstallInSystemExt(),
	)

	// check generated prebuilt_* module specifies correct relative_install_path property
	generatedModule = result.ModuleForTests("system-frameworks_base_data_keyboards-usr_keylayout_subdir-0", "android_arm64_armv8-a").Module()
	etcModule, _ = generatedModule.(*etc.PrebuiltEtc)
	android.AssertStringEquals(
		t,
		"module expected to set correct relative_install_path properties",
		"subdir",
		etcModule.SubDir(),
	)

	// check that prebuilt_* module is not generated for non existing source file
	android.AssertPanicMessageContains(
		t,
		"prebuilt_* module not generated for non existing source file",
		"failed to find module \"system-some_non_existing-etc-0\"",
		func() { result.ModuleForTests("system-some_non_existing-etc-0", "android_arm64_armv8-a") },
	)

	// check that duplicate src file can exist in PRODUCT_COPY_FILES and generates separate modules
	generatedModule0 := result.ModuleForTests("product-device_sample_etc-etc-0", "android_arm64_armv8-a").Module()
	generatedModule1 := result.ModuleForTests("product-device_sample_etc-etc-1", "android_arm64_armv8-a").Module()

	// check that generated prebuilt_* module sets correct srcs and dsts property
	eval := generatedModule0.ConfigurableEvaluator(android.PanickingConfigAndErrorContext(result.TestContext))
	android.AssertBoolEquals(
		t,
		"module expected to set correct srcs property",
		true,
		checkModuleProp(generatedModule0, func(actual interface{}) bool {
			if p, ok := actual.(*etc.PrebuiltEtcProperties); ok {
				srcs := p.Srcs.GetOrDefault(eval, nil)
				return len(srcs) == 1 &&
					srcs[0] == "apns-full-conf.xml"
			}
			return false
		}),
	)
	android.AssertBoolEquals(
		t,
		"module expected to set correct dsts property",
		true,
		checkModuleProp(generatedModule0, func(actual interface{}) bool {
			if p, ok := actual.(*etc.PrebuiltDstsProperties); ok {
				dsts := p.Dsts.GetOrDefault(eval, nil)
				return len(dsts) == 1 &&
					dsts[0] == "apns-conf.xml"
			}
			return false
		}),
	)

	// check that generated prebuilt_* module sets correct srcs and dsts property
	eval = generatedModule1.ConfigurableEvaluator(android.PanickingConfigAndErrorContext(result.TestContext))
	android.AssertBoolEquals(
		t,
		"module expected to set correct srcs property",
		true,
		checkModuleProp(generatedModule1, func(actual interface{}) bool {
			if p, ok := actual.(*etc.PrebuiltEtcProperties); ok {
				srcs := p.Srcs.GetOrDefault(eval, nil)
				return len(srcs) == 1 &&
					srcs[0] == "apns-full-conf.xml"
			}
			return false
		}),
	)
	android.AssertBoolEquals(
		t,
		"module expected to set correct dsts property",
		true,
		checkModuleProp(generatedModule1, func(actual interface{}) bool {
			if p, ok := actual.(*etc.PrebuiltDstsProperties); ok {
				dsts := p.Dsts.GetOrDefault(eval, nil)
				return len(dsts) == 1 &&
					dsts[0] == "apns-conf-2.xml"
			}
			return false
		}),
	)
}
