// Copyright 2021 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 java

import (
	"fmt"
	"path/filepath"
	"regexp"
	"strings"
	"testing"

	"android/soong/android"
)

func TestJavaSdkLibrary(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithPrebuiltApis(map[string][]string{
			"28": {"foo"},
			"29": {"foo"},
			"30": {"bar", "barney", "baz", "betty", "foo", "fred", "quuz", "wilma"},
		}),
		android.PrepareForTestWithBuildFlag("RELEASE_HIDDEN_API_EXPORTABLE_STUBS", "true"),
	).RunTestWithBp(t, `
		droiddoc_exported_dir {
			name: "droiddoc-templates-sdk",
			path: ".",
		}
		java_sdk_library {
			name: "foo",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
		}
		java_sdk_library {
			name: "bar",
			srcs: ["a.java", "b.java"],
			api_packages: ["bar"],
			exclude_kotlinc_generated_files: true,
		}
		java_library {
			name: "baz",
			srcs: ["c.java"],
			libs: ["foo.stubs.system", "bar.stubs"],
			sdk_version: "system_current",
		}
		java_sdk_library {
			name: "barney",
			srcs: ["c.java"],
			api_only: true,
		}
		java_sdk_library {
			name: "betty",
			srcs: ["c.java"],
			shared_library: false,
		}
		java_sdk_library_import {
		    name: "quuz",
				public: {
					jars: ["c.jar"],
					current_api: "api/current.txt",
					removed_api: "api/removed.txt",
				},
		}
		java_sdk_library_import {
		    name: "fred",
				public: {
					jars: ["b.jar"],
				},
		}
		java_sdk_library_import {
		    name: "wilma",
				public: {
					jars: ["b.jar"],
				},
				shared_library: false,
		}
		java_library {
		    name: "qux",
		    srcs: ["c.java"],
		    libs: ["baz", "fred.stubs", "quuz.stubs", "wilma.stubs", "barney.stubs.system", "betty.stubs.system"],
		    sdk_version: "system_current",
		}
		java_library {
			name: "baz-test",
			srcs: ["c.java"],
			libs: ["foo.stubs.test"],
			sdk_version: "test_current",
		}
		java_library {
			name: "baz-29",
			srcs: ["c.java"],
			libs: ["sdk_system_29_foo"],
			sdk_version: "system_29",
		}
		java_library {
			name: "baz-module-30",
			srcs: ["c.java"],
			libs: ["sdk_module-lib_30_foo"],
			sdk_version: "module_30",
		}
	`)

	// check the existence of the internal modules
	foo := result.ModuleForTests("foo", "android_common")
	result.ModuleForTests(apiScopePublic.stubsLibraryModuleName("foo"), "android_common")
	result.ModuleForTests(apiScopeSystem.stubsLibraryModuleName("foo"), "android_common")
	result.ModuleForTests(apiScopeTest.stubsLibraryModuleName("foo"), "android_common")
	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo"), "android_common")
	result.ModuleForTests(apiScopeSystem.stubsSourceModuleName("foo"), "android_common")
	result.ModuleForTests(apiScopeTest.stubsSourceModuleName("foo"), "android_common")
	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo")+".api.contribution", "")
	result.ModuleForTests(apiScopePublic.apiLibraryModuleName("foo"), "android_common")
	result.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common")
	result.ModuleForTests("foo.api.public.28", "")
	result.ModuleForTests("foo.api.system.28", "")
	result.ModuleForTests("foo.api.test.28", "")

	exportedComponentsInfo, _ := android.OtherModuleProvider(result, foo.Module(), android.ExportedComponentsInfoProvider)
	expectedFooExportedComponents := []string{
		"foo-removed.api.combined.public.latest",
		"foo-removed.api.combined.system.latest",
		"foo.api.combined.public.latest",
		"foo.api.combined.system.latest",
		"foo.stubs",
		"foo.stubs.exportable",
		"foo.stubs.exportable.system",
		"foo.stubs.exportable.test",
		"foo.stubs.source",
		"foo.stubs.source.system",
		"foo.stubs.source.test",
		"foo.stubs.system",
		"foo.stubs.test",
	}
	android.AssertArrayString(t, "foo exported components", expectedFooExportedComponents, exportedComponentsInfo.Components)

	bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac")
	// tests if baz is actually linked to the stubs lib
	android.AssertStringDoesContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.system.jar")
	// ... and not to the impl lib
	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.jar")
	// test if baz is not linked to the system variant of foo
	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.jar")

	bazTestJavac := result.ModuleForTests("baz-test", "android_common").Rule("javac")
	// tests if baz-test is actually linked to the test stubs lib
	android.AssertStringDoesContain(t, "baz-test javac classpath", bazTestJavac.Args["classpath"], "foo.stubs.test.jar")

	baz29Javac := result.ModuleForTests("baz-29", "android_common").Rule("javac")
	// tests if baz-29 is actually linked to the system 29 stubs lib
	android.AssertStringDoesContain(t, "baz-29 javac classpath", baz29Javac.Args["classpath"], "prebuilts/sdk/sdk_system_29_foo/android_common/combined/sdk_system_29_foo.jar")

	bazModule30Javac := result.ModuleForTests("baz-module-30", "android_common").Rule("javac")
	// tests if "baz-module-30" is actually linked to the module 30 stubs lib
	android.AssertStringDoesContain(t, "baz-module-30 javac classpath", bazModule30Javac.Args["classpath"], "prebuilts/sdk/sdk_module-lib_30_foo/android_common/combined/sdk_module-lib_30_foo.jar")

	// test if baz has exported SDK lib names foo and bar to qux
	qux := result.ModuleForTests("qux", "android_common")
	if quxLib, ok := qux.Module().(*Library); ok {
		requiredSdkLibs, optionalSdkLibs := quxLib.ClassLoaderContexts().UsesLibs()
		android.AssertDeepEquals(t, "qux exports (required)", []string{"fred", "quuz", "foo", "bar"}, requiredSdkLibs)
		android.AssertDeepEquals(t, "qux exports (optional)", []string{}, optionalSdkLibs)
	}

	// test if quuz have created the api_contribution module
	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("quuz")+".api.contribution", "")

	fooImplDexJar := result.ModuleForTests("foo.impl", "android_common").Rule("d8")
	// tests if kotlinc generated files are NOT excluded from output of foo.impl.
	android.AssertStringDoesNotContain(t, "foo.impl dex", fooImplDexJar.BuildParams.Args["mergeZipsFlags"], "-stripFile META-INF/*.kotlin_module")

	barImplDexJar := result.ModuleForTests("bar.impl", "android_common").Rule("d8")
	// tests if kotlinc generated files are excluded from output of bar.impl.
	android.AssertStringDoesContain(t, "bar.impl dex", barImplDexJar.BuildParams.Args["mergeZipsFlags"], "-stripFile META-INF/*.kotlin_module")
}

func TestJavaSdkLibrary_UpdatableLibrary(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithPrebuiltApis(map[string][]string{
			"28": {"foo"},
			"29": {"foo"},
			"30": {"foo", "fooUpdatable", "fooUpdatableErr"},
		}),
		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
			variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V", "W", "X"}
		}),
	).RunTestWithBp(t,
		`
		java_sdk_library {
			name: "fooUpdatable",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
			on_bootclasspath_since: "U",
			on_bootclasspath_before: "V",
			min_device_sdk: "W",
			max_device_sdk: "X",
			min_sdk_version: "S",
		}
		java_sdk_library {
			name: "foo",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
		}
`)

	// test that updatability attributes are passed on correctly
	fooUpdatable := result.ModuleForTests("fooUpdatable.xml", "android_common").Output("fooUpdatable.xml")
	fooUpdatableContents := android.ContentFromFileRuleForTests(t, result.TestContext, fooUpdatable)
	android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `on-bootclasspath-since="U"`)
	android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `on-bootclasspath-before="V"`)
	android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `min-device-sdk="W"`)
	android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `max-device-sdk="X"`)

	// double check that updatability attributes are not written if they don't exist in the bp file
	// the permissions file for the foo library defined above
	fooPermissions := result.ModuleForTests("foo.xml", "android_common").Output("foo.xml")
	fooPermissionsContents := android.ContentFromFileRuleForTests(t, result.TestContext, fooPermissions)
	android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `on-bootclasspath-since`)
	android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `on-bootclasspath-before`)
	android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `min-device-sdk`)
	android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `max-device-sdk`)
}

func TestJavaSdkLibrary_UpdatableLibrary_Validation_ValidVersion(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithPrebuiltApis(map[string][]string{
			"30": {"fooUpdatable", "fooUpdatableErr"},
		}),
	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(
		[]string{
			`on_bootclasspath_since: "aaa" could not be parsed as an integer and is not a recognized codename`,
			`on_bootclasspath_before: "bbc" could not be parsed as an integer and is not a recognized codename`,
			`min_device_sdk: "ccc" could not be parsed as an integer and is not a recognized codename`,
			`max_device_sdk: "current" is not an allowed value for this attribute`,
		})).RunTestWithBp(t,
		`
	java_sdk_library {
			name: "fooUpdatableErr",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
			on_bootclasspath_since: "aaa",
			on_bootclasspath_before: "bbc",
			min_device_sdk: "ccc",
			max_device_sdk: "current",
		}
`)
}

func TestJavaSdkLibrary_UpdatableLibrary_Validation_AtLeastTAttributes(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithPrebuiltApis(map[string][]string{
			"28": {"foo"},
		}),
	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(
		[]string{
			"on_bootclasspath_since: Attribute value needs to be at least T",
			"on_bootclasspath_before: Attribute value needs to be at least T",
			"min_device_sdk: Attribute value needs to be at least T",
			"max_device_sdk: Attribute value needs to be at least T",
		},
	)).RunTestWithBp(t,
		`
		java_sdk_library {
			name: "foo",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
			on_bootclasspath_since: "S",
			on_bootclasspath_before: "S",
			min_device_sdk: "S",
			max_device_sdk: "S",
			min_sdk_version: "S",
		}
`)
}

func TestJavaSdkLibrary_UpdatableLibrary_Validation_MinAndMaxDeviceSdk(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithPrebuiltApis(map[string][]string{
			"28": {"foo"},
		}),
		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
			variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V"}
		}),
	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(
		[]string{
			"min_device_sdk can't be greater than max_device_sdk",
		},
	)).RunTestWithBp(t,
		`
		java_sdk_library {
			name: "foo",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
			min_device_sdk: "V",
			max_device_sdk: "U",
			min_sdk_version: "S",
		}
`)
}

func TestJavaSdkLibrary_UpdatableLibrary_Validation_MinAndMaxDeviceSdkAndModuleMinSdk(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithPrebuiltApis(map[string][]string{
			"28": {"foo"},
		}),
		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
			variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V"}
		}),
	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(
		[]string{
			regexp.QuoteMeta("min_device_sdk: Can't be less than module's min sdk (V)"),
			regexp.QuoteMeta("max_device_sdk: Can't be less than module's min sdk (V)"),
		},
	)).RunTestWithBp(t,
		`
		java_sdk_library {
			name: "foo",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
			min_device_sdk: "U",
			max_device_sdk: "U",
			min_sdk_version: "V",
		}
`)
}

func TestJavaSdkLibrary_UpdatableLibrary_usesNewTag(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithPrebuiltApis(map[string][]string{
			"30": {"foo"},
		}),
	).RunTestWithBp(t,
		`
		java_sdk_library {
			name: "foo",
			srcs: ["a.java", "b.java"],
			min_device_sdk: "Tiramisu",
			min_sdk_version: "S",
		}
`)
	// test that updatability attributes are passed on correctly
	fooUpdatable := result.ModuleForTests("foo.xml", "android_common").Output("foo.xml")
	fooUpdatableContents := android.ContentFromFileRuleForTests(t, result.TestContext, fooUpdatable)
	android.AssertStringDoesContain(t, "foo.xml contents", fooUpdatableContents, `<apex-library`)
	android.AssertStringDoesNotContain(t, "foo.xml contents", fooUpdatableContents, `<library`)
}

func TestJavaSdkLibrary_StubOrImplOnlyLibs(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("sdklib"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "sdklib",
			srcs: ["a.java"],
			libs: ["lib"],
			static_libs: ["static-lib"],
			impl_only_libs: ["impl-only-lib"],
			stub_only_libs: ["stub-only-lib"],
			stub_only_static_libs: ["stub-only-static-lib"],
		}
		java_defaults {
			name: "defaults",
			srcs: ["a.java"],
			sdk_version: "current",
		}
		java_library { name: "lib", defaults: ["defaults"] }
		java_library { name: "static-lib", defaults: ["defaults"] }
		java_library { name: "impl-only-lib", defaults: ["defaults"] }
		java_library { name: "stub-only-lib", defaults: ["defaults"] }
		java_library { name: "stub-only-static-lib", defaults: ["defaults"] }
		`)
	var expectations = []struct {
		lib               string
		on_impl_classpath bool
		on_stub_classpath bool
		in_impl_combined  bool
		in_stub_combined  bool
	}{
		{lib: "lib", on_impl_classpath: true},
		{lib: "static-lib", in_impl_combined: true},
		{lib: "impl-only-lib", on_impl_classpath: true},
		{lib: "stub-only-lib", on_stub_classpath: true},
		{lib: "stub-only-static-lib", in_stub_combined: true},
	}
	verify := func(sdklib, dep string, cp, combined bool) {
		sdklibCp := result.ModuleForTests(sdklib, "android_common").Rule("javac").Args["classpath"]
		expected := cp || combined // Every combined jar is also on the classpath.
		android.AssertStringContainsEquals(t, "bad classpath for "+sdklib, sdklibCp, "/"+dep+".jar", expected)

		combineJarInputs := result.ModuleForTests(sdklib, "android_common").Rule("combineJar").Inputs.Strings()
		depPath := filepath.Join("out", "soong", ".intermediates", dep, "android_common", "turbine-combined", dep+".jar")
		android.AssertStringListContainsEquals(t, "bad combined inputs for "+sdklib, combineJarInputs, depPath, combined)
	}
	for _, expectation := range expectations {
		verify("sdklib.impl", expectation.lib, expectation.on_impl_classpath, expectation.in_impl_combined)

		stubName := apiScopePublic.sourceStubsLibraryModuleName("sdklib")
		verify(stubName, expectation.lib, expectation.on_stub_classpath, expectation.in_stub_combined)
	}
}

func TestJavaSdkLibrary_DoNotAccessImplWhenItIsNotBuilt(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java"],
			api_only: true,
			public: {
				enabled: true,
			},
		}

		java_library {
			name: "bar",
			srcs: ["b.java"],
			libs: ["foo.stubs"],
		}
		`)

	// The bar library should depend on the stubs jar.
	barLibrary := result.ModuleForTests("bar", "android_common").Rule("javac")
	if expected, actual := `^-classpath .*:out/soong/[^:]*/turbine-combined/foo\.stubs\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
		t.Errorf("expected %q, found %#q", expected, actual)
	}
}

func TestJavaSdkLibrary_AccessOutputFiles(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java"],
			api_packages: ["foo"],
			annotations_enabled: true,
			public: {
				enabled: true,
			},
		}
		java_library {
			name: "bar",
			srcs: ["b.java", ":foo{.public.stubs.source}"],
			java_resources: [":foo{.public.annotations.zip}"],
		}
		`)
}

func TestJavaSdkLibrary_AccessOutputFiles_NoAnnotations(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
	).
		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "bar" variant "android_common": unsupported module reference tag ".public.annotations.zip"`)).
		RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java"],
			api_packages: ["foo"],
			public: {
				enabled: true,
			},
		}

		java_library {
			name: "bar",
			srcs: ["b.java", ":foo{.public.stubs.source}"],
			java_resources: [":foo{.public.annotations.zip}"],
		}
		`)
}

func TestJavaSdkLibrary_AccessOutputFiles_MissingScope(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
	).
		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "bar" variant "android_common": unsupported module reference tag ".system.stubs.source"`)).
		RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java"],
			api_packages: ["foo"],
			public: {
				enabled: true,
			},
		}

		java_library {
			name: "bar",
			srcs: ["b.java", ":foo{.system.stubs.source}"],
		}
		`)
}

func TestJavaSdkLibrary_Deps(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("sdklib"),
		android.PrepareForTestWithBuildFlag("RELEASE_HIDDEN_API_EXPORTABLE_STUBS", "true"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "sdklib",
			srcs: ["a.java"],
			sdk_version: "none",
			system_modules: "none",
			public: {
				enabled: true,
			},
		}
		`)

	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
		`dex2oatd`,
		`sdklib-removed.api.combined.public.latest`,
		`sdklib.api.combined.public.latest`,
		`sdklib.impl`,
		`sdklib.stubs`,
		`sdklib.stubs.exportable`,
		`sdklib.stubs.source`,
		`sdklib.xml`,
	})
}

func TestJavaSdkLibraryImport_AccessOutputFiles(t *testing.T) {
	prepareForJavaTest.RunTestWithBp(t, `
		java_sdk_library_import {
			name: "foo",
			public: {
				jars: ["a.jar"],
				stub_srcs: ["a.java"],
				current_api: "api/current.txt",
				removed_api: "api/removed.txt",
				annotations: "x/annotations.zip",
			},
		}

		java_library {
			name: "bar",
			srcs: [":foo{.public.stubs.source}"],
			java_resources: [
				":foo{.public.api.txt}",
				":foo{.public.removed-api.txt}",
				":foo{.public.annotations.zip}",
			],
		}
		`)
}

func TestJavaSdkLibraryImport_AccessOutputFiles_Invalid(t *testing.T) {
	bp := `
		java_sdk_library_import {
			name: "foo",
			public: {
				jars: ["a.jar"],
			},
		}
		`

	t.Run("stubs.source", func(t *testing.T) {
		prepareForJavaTest.
			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo" is not a SourceFileProducer or having valid output file for tag ".public.stubs.source"`)).
			RunTestWithBp(t, bp+`
				java_library {
					name: "bar",
					srcs: [":foo{.public.stubs.source}"],
					java_resources: [
						":foo{.public.api.txt}",
						":foo{.public.removed-api.txt}",
					],
				}
			`)
	})

	t.Run("api.txt", func(t *testing.T) {
		prepareForJavaTest.
			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo" is not a SourceFileProducer or having valid output file for tag ".public.api.txt"`)).
			RunTestWithBp(t, bp+`
				java_library {
					name: "bar",
					srcs: ["a.java"],
					java_resources: [
						":foo{.public.api.txt}",
					],
				}
			`)
	})

	t.Run("removed-api.txt", func(t *testing.T) {
		prepareForJavaTest.
			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo" is not a SourceFileProducer or having valid output file for tag ".public.removed-api.txt"`)).
			RunTestWithBp(t, bp+`
				java_library {
					name: "bar",
					srcs: ["a.java"],
					java_resources: [
						":foo{.public.removed-api.txt}",
					],
				}
			`)
	})
}

func TestJavaSdkLibrary_InvalidScopes(t *testing.T) {
	prepareForJavaTest.
		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo": enabled api scope "system" depends on disabled scope "public"`)).
		RunTestWithBp(t, `
			java_sdk_library {
				name: "foo",
				srcs: ["a.java", "b.java"],
				api_packages: ["foo"],
				// Explicitly disable public to test the check that ensures the set of enabled
				// scopes is consistent.
				public: {
					enabled: false,
				},
				system: {
					enabled: true,
				},
			}
		`)
}

func TestJavaSdkLibrary_SdkVersion_ForScope(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
			system: {
				enabled: true,
				sdk_version: "module_current",
			},
		}
		`)
}

func TestJavaSdkLibrary_ModuleLib(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
			system: {
				enabled: true,
			},
			module_lib: {
				enabled: true,
			},
		}
		`)
}

func TestJavaSdkLibrary_SystemServer(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
			system: {
				enabled: true,
			},
			system_server: {
				enabled: true,
			},
		}
		`)
}

func TestJavaSdkLibrary_SystemServer_AccessToStubScopeLibs(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo-public", "foo-system", "foo-module-lib", "foo-system-server"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo-public",
			srcs: ["a.java"],
			api_packages: ["foo"],
			public: {
				enabled: true,
			},
		}

		java_sdk_library {
			name: "foo-system",
			srcs: ["a.java"],
			api_packages: ["foo"],
			system: {
				enabled: true,
			},
		}

		java_sdk_library {
			name: "foo-module-lib",
			srcs: ["a.java"],
			api_packages: ["foo"],
			system: {
				enabled: true,
			},
			module_lib: {
				enabled: true,
			},
		}

		java_sdk_library {
			name: "foo-system-server",
			srcs: ["a.java"],
			api_packages: ["foo"],
			system_server: {
				enabled: true,
			},
		}

		java_library {
			name: "bar",
			srcs: ["a.java"],
			libs: ["foo-public.stubs", "foo-system.stubs.system", "foo-module-lib.stubs.module_lib", "foo-system-server.stubs.system_server"],
			sdk_version: "system_server_current",
		}
		`)

	stubsPath := func(name string, scope *apiScope) string {
		name = scope.stubsLibraryModuleName(name)
		return fmt.Sprintf("out/soong/.intermediates/%[1]s/android_common/turbine-combined/%[1]s.jar", name)
	}

	// The bar library should depend on the highest (where system server is highest and public is
	// lowest) API scopes provided by each of the foo-* modules. The highest API scope provided by the
	// foo-<x> module is <x>.
	barLibrary := result.ModuleForTests("bar", "android_common").Rule("javac")
	stubLibraries := []string{
		stubsPath("foo-public", apiScopePublic),
		stubsPath("foo-system", apiScopeSystem),
		stubsPath("foo-module-lib", apiScopeModuleLib),
		stubsPath("foo-system-server", apiScopeSystemServer),
	}
	expectedPattern := fmt.Sprintf(`^-classpath .*:\Q%s\E$`, strings.Join(stubLibraries, ":"))
	if expected, actual := expectedPattern, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
		t.Errorf("expected pattern %q to match %#q", expected, actual)
	}
}

func TestJavaSdkLibraryImport(t *testing.T) {
	result := prepareForJavaTest.RunTestWithBp(t, `
		java_library {
			name: "foo",
			srcs: ["a.java"],
			libs: ["sdklib.stubs"],
			sdk_version: "current",
		}

		java_library {
			name: "foo.system",
			srcs: ["a.java"],
			libs: ["sdklib.stubs.system"],
			sdk_version: "system_current",
		}

		java_library {
			name: "foo.test",
			srcs: ["a.java"],
			libs: ["sdklib.stubs.test"],
			sdk_version: "test_current",
		}

		java_sdk_library_import {
			name: "sdklib",
			public: {
				jars: ["a.jar"],
			},
			system: {
				jars: ["b.jar"],
			},
			test: {
				jars: ["c.jar"],
				stub_srcs: ["c.java"],
			},
		}
		`)

	for _, scope := range []string{"", ".system", ".test"} {
		fooModule := result.ModuleForTests("foo"+scope, "android_common")
		javac := fooModule.Rule("javac")

		sdklibStubsJar := result.ModuleForTests("sdklib.stubs"+scope, "android_common").Output("combined/sdklib.stubs" + scope + ".jar").Output
		android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], sdklibStubsJar.String())
	}

	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
		`all_apex_contributions`,
		`dex2oatd`,
		`prebuilt_sdklib.stubs`,
		`prebuilt_sdklib.stubs.source.test`,
		`prebuilt_sdklib.stubs.system`,
		`prebuilt_sdklib.stubs.test`,
	})
}

func TestJavaSdkLibraryImport_WithSource(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("sdklib"),
		android.PrepareForTestWithBuildFlag("RELEASE_HIDDEN_API_EXPORTABLE_STUBS", "true"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "sdklib",
			srcs: ["a.java"],
			sdk_version: "none",
			system_modules: "none",
			public: {
				enabled: true,
			},
		}

		java_sdk_library_import {
			name: "sdklib",
			public: {
				jars: ["a.jar"],
			},
		}
		`)

	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
		`dex2oatd`,
		`prebuilt_sdklib`,
		`sdklib-removed.api.combined.public.latest`,
		`sdklib.api.combined.public.latest`,
		`sdklib.impl`,
		`sdklib.stubs`,
		`sdklib.stubs.exportable`,
		`sdklib.stubs.source`,
		`sdklib.xml`,
	})

	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
		`all_apex_contributions`,
		`prebuilt_sdklib.stubs`,
		`sdklib.impl`,
		// This should be prebuilt_sdklib.stubs but is set to sdklib.stubs because the
		// dependency is added after prebuilts may have been renamed and so has to use
		// the renamed name.
		`sdklib.xml`,
	})
}

func testJavaSdkLibraryImport_Preferred(t *testing.T, prefer string, preparer android.FixturePreparer) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("sdklib"),
		preparer,
		android.PrepareForTestWithBuildFlag("RELEASE_HIDDEN_API_EXPORTABLE_STUBS", "true"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "sdklib",
			srcs: ["a.java"],
			sdk_version: "none",
			system_modules: "none",
			public: {
				enabled: true,
			},
		}

		java_sdk_library_import {
			name: "sdklib",
			`+prefer+`
			public: {
				jars: ["a.jar"],
				stub_srcs: ["a.java"],
				current_api: "current.txt",
				removed_api: "removed.txt",
				annotations: "annotations.zip",
			},
		}

		java_library {
			name: "combined",
			static_libs: [
				"sdklib.stubs",
			],
			java_resources: [
				":sdklib.stubs.source",
				":sdklib{.public.api.txt}",
				":sdklib{.public.removed-api.txt}",
				":sdklib{.public.annotations.zip}",
			],
			sdk_version: "none",
			system_modules: "none",
		}

		java_library {
			name: "public",
			srcs: ["a.java"],
			libs: ["sdklib.stubs"],
			sdk_version: "current",
		}
		`)

	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
		`prebuilt_sdklib`,
		`sdklib-removed.api.combined.public.latest`,
		`sdklib.api.combined.public.latest`,
		`sdklib.impl`,
		`sdklib.stubs`,
		`sdklib.stubs.exportable`,
		`sdklib.stubs.source`,
		`sdklib.xml`,
	})

	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
		`all_apex_contributions`,
		`dex2oatd`,
		`prebuilt_sdklib.stubs`,
		`prebuilt_sdklib.stubs.source`,
		`sdklib.impl`,
		`sdklib.xml`,
	})

	// Make sure that dependencies on child modules use the prebuilt when preferred.
	CheckModuleDependencies(t, result.TestContext, "combined", "android_common", []string{
		// Each use of :sdklib{...} adds a dependency onto prebuilt_sdklib.
		`prebuilt_sdklib`,
		`prebuilt_sdklib`,
		`prebuilt_sdklib`,
		`prebuilt_sdklib.stubs`,
		`prebuilt_sdklib.stubs.source`,
	})

	// Make sure that dependencies on sdklib that resolve to one of the child libraries use the
	// prebuilt library.
	public := result.ModuleForTests("public", "android_common")
	rule := public.Output("javac/public.jar")
	inputs := rule.Implicits.Strings()
	expected := "out/soong/.intermediates/prebuilt_sdklib.stubs/android_common/combined/sdklib.stubs.jar"
	if !android.InList(expected, inputs) {
		t.Errorf("expected %q to contain %q", inputs, expected)
	}
}

func TestJavaSdkLibraryImport_Preferred(t *testing.T) {
	t.Run("prefer", func(t *testing.T) {
		testJavaSdkLibraryImport_Preferred(t, "prefer: true,", android.NullFixturePreparer)
	})
}

// If a module is listed in `mainline_module_contributions, it should be used
// It will supersede any other source vs prebuilt selection mechanism like `prefer` attribute
func TestSdkLibraryImport_MetadataModuleSupersedesPreferred(t *testing.T) {
	bp := `
		apex_contributions {
			name: "my_mainline_module_contributions",
			api_domain: "my_mainline_module",
			contents: [
				// legacy mechanism prefers the prebuilt
				// mainline_module_contributions supersedes this since source is listed explicitly
				"sdklib.prebuilt_preferred_using_legacy_flags",

				// legacy mechanism prefers the source
				// mainline_module_contributions supersedes this since prebuilt is listed explicitly
				"prebuilt_sdklib.source_preferred_using_legacy_flags",
			],
		}
		java_sdk_library {
			name: "sdklib.prebuilt_preferred_using_legacy_flags",
			srcs: ["a.java"],
			sdk_version: "none",
			system_modules: "none",
			public: {
				enabled: true,
			},
			system: {
				enabled: true,
			}
		}
		java_sdk_library_import {
			name: "sdklib.prebuilt_preferred_using_legacy_flags",
			prefer: true, // prebuilt is preferred using legacy mechanism
			public: {
				jars: ["a.jar"],
				stub_srcs: ["a.java"],
				current_api: "current.txt",
				removed_api: "removed.txt",
				annotations: "annotations.zip",
			},
			system: {
				jars: ["a.jar"],
				stub_srcs: ["a.java"],
				current_api: "current.txt",
				removed_api: "removed.txt",
				annotations: "annotations.zip",
			},
		}
		java_sdk_library {
			name: "sdklib.source_preferred_using_legacy_flags",
			srcs: ["a.java"],
			sdk_version: "none",
			system_modules: "none",
			public: {
				enabled: true,
			},
			system: {
				enabled: true,
			}
		}
		java_sdk_library_import {
			name: "sdklib.source_preferred_using_legacy_flags",
			prefer: false, // source is preferred using legacy mechanism
			public: {
				jars: ["a.jar"],
				stub_srcs: ["a.java"],
				current_api: "current.txt",
				removed_api: "removed.txt",
				annotations: "annotations.zip",
			},
			system: {
				jars: ["a.jar"],
				stub_srcs: ["a.java"],
				current_api: "current.txt",
				removed_api: "removed.txt",
				annotations: "annotations.zip",
			},
		}

		// rdeps
		java_library {
			name: "public",
			srcs: ["a.java"],
			libs: [
				// this should get source since source is listed in my_mainline_module_contributions
				"sdklib.prebuilt_preferred_using_legacy_flags.stubs",
				"sdklib.prebuilt_preferred_using_legacy_flags.stubs.system",

				// this should get prebuilt since source is listed in my_mainline_module_contributions
				"sdklib.source_preferred_using_legacy_flags.stubs",
				"sdklib.source_preferred_using_legacy_flags.stubs.system",

			],
		}
	`
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("sdklib.source_preferred_using_legacy_flags", "sdklib.prebuilt_preferred_using_legacy_flags"),
		android.PrepareForTestWithBuildFlag("RELEASE_APEX_CONTRIBUTIONS_ADSERVICES", "my_mainline_module_contributions"),
	).RunTestWithBp(t, bp)

	// Make sure that rdeps get the correct source vs prebuilt based on mainline_module_contributions
	public := result.ModuleForTests("public", "android_common")
	rule := public.Output("javac/public.jar")
	inputs := rule.Implicits.Strings()
	expectedInputs := []string{
		// source
		"out/soong/.intermediates/sdklib.prebuilt_preferred_using_legacy_flags.stubs/android_common/turbine-combined/sdklib.prebuilt_preferred_using_legacy_flags.stubs.jar",
		"out/soong/.intermediates/sdklib.prebuilt_preferred_using_legacy_flags.stubs.system/android_common/turbine-combined/sdklib.prebuilt_preferred_using_legacy_flags.stubs.system.jar",

		// prebuilt
		"out/soong/.intermediates/prebuilt_sdklib.source_preferred_using_legacy_flags.stubs/android_common/combined/sdklib.source_preferred_using_legacy_flags.stubs.jar",
		"out/soong/.intermediates/prebuilt_sdklib.source_preferred_using_legacy_flags.stubs.system/android_common/combined/sdklib.source_preferred_using_legacy_flags.stubs.system.jar",
	}
	for _, expected := range expectedInputs {
		if !android.InList(expected, inputs) {
			t.Errorf("expected %q to contain %q", inputs, expected)
		}
	}
}

func TestJavaSdkLibraryDist(t *testing.T) {
	result := android.GroupFixturePreparers(
		PrepareForTestWithJavaBuildComponents,
		PrepareForTestWithJavaDefaultModules,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis(
			"sdklib_no_group",
			"sdklib_group_foo",
			"sdklib_owner_foo",
			"foo"),
		android.PrepareForTestWithBuildFlag("RELEASE_HIDDEN_API_EXPORTABLE_STUBS", "true"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "sdklib_no_group",
			srcs: ["foo.java"],
		}

		java_sdk_library {
			name: "sdklib_group_foo",
			srcs: ["foo.java"],
			dist_group: "foo",
		}

		java_sdk_library {
			name: "sdklib_owner_foo",
			srcs: ["foo.java"],
			owner: "foo",
		}

		java_sdk_library {
			name: "sdklib_stem_foo",
			srcs: ["foo.java"],
			dist_stem: "foo",
		}
	`)

	type testCase struct {
		module   string
		distDir  string
		distStem string
	}
	testCases := []testCase{
		{
			module:   "sdklib_no_group",
			distDir:  "apistubs/unknown/public",
			distStem: "sdklib_no_group.jar",
		},
		{
			module:   "sdklib_group_foo",
			distDir:  "apistubs/foo/public",
			distStem: "sdklib_group_foo.jar",
		},
		{
			// Owner doesn't affect distDir after b/186723288.
			module:   "sdklib_owner_foo",
			distDir:  "apistubs/unknown/public",
			distStem: "sdklib_owner_foo.jar",
		},
		{
			module:   "sdklib_stem_foo",
			distDir:  "apistubs/unknown/public",
			distStem: "foo.jar",
		},
	}

	for _, tt := range testCases {
		t.Run(tt.module, func(t *testing.T) {
			m := result.ModuleForTests(apiScopePublic.exportableStubsLibraryModuleName(tt.module), "android_common").Module().(*Library)
			dists := m.Dists()
			if len(dists) != 1 {
				t.Fatalf("expected exactly 1 dist entry, got %d", len(dists))
			}
			if g, w := String(dists[0].Dir), tt.distDir; g != w {
				t.Errorf("expected dist dir %q, got %q", w, g)
			}
			if g, w := String(dists[0].Dest), tt.distStem; g != w {
				t.Errorf("expected dist stem %q, got %q", w, g)
			}
		})
	}
}

func TestSdkLibrary_CheckMinSdkVersion(t *testing.T) {
	preparer := android.GroupFixturePreparers(
		PrepareForTestWithJavaBuildComponents,
		PrepareForTestWithJavaDefaultModules,
		PrepareForTestWithJavaSdkLibraryFiles,
	)

	preparer.RunTestWithBp(t, `
		java_sdk_library {
			name: "sdklib",
			srcs: ["a.java"],
			static_libs: ["util"],
			min_sdk_version: "30",
			unsafe_ignore_missing_latest_api: true,
		}

		java_library {
			name: "util",
			srcs: ["a.java"],
			min_sdk_version: "30",
		}
	`)

	preparer.
		RunTestWithBp(t, `
			java_sdk_library {
				name: "sdklib",
				srcs: ["a.java"],
				libs: ["util"],
				impl_only_libs: ["util"],
				stub_only_libs: ["util"],
				stub_only_static_libs: ["util"],
				min_sdk_version: "30",
				unsafe_ignore_missing_latest_api: true,
			}

			java_library {
				name: "util",
				srcs: ["a.java"],
			}
		`)

	preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "util".*should support min_sdk_version\(30\)`)).
		RunTestWithBp(t, `
			java_sdk_library {
				name: "sdklib",
				srcs: ["a.java"],
				static_libs: ["util"],
				min_sdk_version: "30",
				unsafe_ignore_missing_latest_api: true,
			}

			java_library {
				name: "util",
				srcs: ["a.java"],
				min_sdk_version: "31",
			}
		`)

	preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "another_util".*should support min_sdk_version\(30\)`)).
		RunTestWithBp(t, `
			java_sdk_library {
				name: "sdklib",
				srcs: ["a.java"],
				static_libs: ["util"],
				min_sdk_version: "30",
				unsafe_ignore_missing_latest_api: true,
			}

			java_library {
				name: "util",
				srcs: ["a.java"],
				static_libs: ["another_util"],
				min_sdk_version: "30",
			}

			java_library {
				name: "another_util",
				srcs: ["a.java"],
				min_sdk_version: "31",
			}
		`)
}

func TestJavaSdkLibrary_StubOnlyLibs_PassedToDroidstubs(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java"],
			public: {
				enabled: true,
			},
			stub_only_libs: ["bar-lib"],
		}

		java_library {
			name: "bar-lib",
			srcs: ["b.java"],
		}
		`)

	// The foo.stubs.source should depend on bar-lib
	fooStubsSources := result.ModuleForTests("foo.stubs.source", "android_common").Module().(*Droidstubs)
	eval := fooStubsSources.ConfigurableEvaluator(android.PanickingConfigAndErrorContext(result.TestContext))
	android.AssertStringListContains(t, "foo stubs should depend on bar-lib", fooStubsSources.Javadoc.properties.Libs.GetOrDefault(eval, nil), "bar-lib")
}

func TestJavaSdkLibrary_Scope_Libs_PassedToDroidstubs(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java"],
			public: {
				enabled: true,
				libs: ["bar-lib"],
			},
		}

		java_library {
			name: "bar-lib",
			srcs: ["b.java"],
		}
		`)

	// The foo.stubs.source should depend on bar-lib
	fooStubsSources := result.ModuleForTests("foo.stubs.source", "android_common").Module().(*Droidstubs)
	eval := fooStubsSources.ConfigurableEvaluator(android.PanickingConfigAndErrorContext(result.TestContext))
	android.AssertStringListContains(t, "foo stubs should depend on bar-lib", fooStubsSources.Javadoc.properties.Libs.GetOrDefault(eval, nil), "bar-lib")
}

func TestJavaSdkLibrary_ApiLibrary(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
			system: {
				enabled: true,
			},
			module_lib: {
				enabled: true,
			},
			test: {
				enabled: true,
			},
		}
	`)

	testCases := []struct {
		scope            *apiScope
		apiContributions []string
	}{
		{
			scope:            apiScopePublic,
			apiContributions: []string{"foo.stubs.source.api.contribution"},
		},
		{
			scope:            apiScopeSystem,
			apiContributions: []string{"foo.stubs.source.system.api.contribution", "foo.stubs.source.api.contribution"},
		},
		{
			scope:            apiScopeTest,
			apiContributions: []string{"foo.stubs.source.test.api.contribution", "foo.stubs.source.system.api.contribution", "foo.stubs.source.api.contribution"},
		},
		{
			scope:            apiScopeModuleLib,
			apiContributions: []string{"foo.stubs.source.module_lib.api.contribution", "foo.stubs.source.system.api.contribution", "foo.stubs.source.api.contribution"},
		},
	}

	for _, c := range testCases {
		m := result.ModuleForTests(c.scope.apiLibraryModuleName("foo"), "android_common").Module().(*ApiLibrary)
		android.AssertArrayString(t, "Module expected to contain api contributions", c.apiContributions, m.properties.Api_contributions)
	}
}

func TestStaticDepStubLibrariesVisibility(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
		android.FixtureMergeMockFs(
			map[string][]byte{
				"A.java": nil,
				"dir/Android.bp": []byte(
					`
					java_library {
						name: "bar",
						srcs: ["A.java"],
						libs: ["foo.stubs.from-source"],
					}
					`),
				"dir/A.java": nil,
			},
		).ExtendWithErrorHandler(
			android.FixtureExpectsAtLeastOneErrorMatchingPattern(
				`module "bar" variant "android_common": depends on //.:foo.stubs.from-source which is not visible to this module`)),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["A.java"],
		}
	`)
}

func TestSdkLibraryDependency(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithPrebuiltApis(map[string][]string{
			"30": {"bar", "foo"},
		}),
	).RunTestWithBp(t,
		`
		java_sdk_library {
			name: "foo",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
		}

		java_sdk_library {
			name: "bar",
			srcs: ["c.java", "b.java"],
			libs: [
				"foo.stubs",
			],
			uses_libs: [
				"foo",
			],
		}
`)

	barPermissions := result.ModuleForTests("bar.xml", "android_common").Output("bar.xml")
	barContents := android.ContentFromFileRuleForTests(t, result.TestContext, barPermissions)
	android.AssertStringDoesContain(t, "bar.xml java_sdk_xml command", barContents, `dependency="foo"`)
}

func TestSdkLibraryExportableStubsLibrary(t *testing.T) {
	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
	).RunTestWithBp(t, `
		aconfig_declarations {
			name: "bar",
			package: "com.example.package",
			container: "com.android.foo",
			srcs: [
				"bar.aconfig",
			],
		}
		java_sdk_library {
			name: "foo",
			srcs: ["a.java", "b.java"],
			api_packages: ["foo"],
			system: {
				enabled: true,
			},
			module_lib: {
				enabled: true,
			},
			test: {
				enabled: true,
			},
			aconfig_declarations: [
				"bar",
			],
		}
	`)

	exportableStubsLibraryModuleName := apiScopePublic.exportableStubsLibraryModuleName("foo")
	exportableSourceStubsLibraryModuleName := apiScopePublic.exportableSourceStubsLibraryModuleName("foo")

	// Check modules generation
	result.ModuleForTests(exportableStubsLibraryModuleName, "android_common")
	result.ModuleForTests(exportableSourceStubsLibraryModuleName, "android_common")

	// Check static lib dependency
	android.AssertBoolEquals(t, "exportable top level stubs library module depends on the"+
		"exportable source stubs library module", true,
		CheckModuleHasDependencyWithTag(t, result.TestContext, exportableStubsLibraryModuleName,
			"android_common", staticLibTag, exportableSourceStubsLibraryModuleName),
	)
}

// For java libraries depending on java_sdk_library(_import) via libs, assert that
// rdep gets stubs of source if source is listed in apex_contributions and prebuilt has prefer (legacy mechanism)
func TestStubResolutionOfJavaSdkLibraryInLibs(t *testing.T) {
	bp := `
		apex_contributions {
			name: "my_mainline_module_contributions",
			api_domain: "my_mainline_module",
			contents: ["sdklib"], // source is selected using apex_contributions, but prebuilt is selected using prefer
		}
		java_sdk_library {
			name: "sdklib",
			srcs: ["a.java"],
			sdk_version: "none",
			system_modules: "none",
			public: {
				enabled: true,
			},
		}
		java_sdk_library_import {
			name: "sdklib",
			public: {
				jars: ["a.jar"],
				stub_srcs: ["a.java"],
				current_api: "current.txt",
				removed_api: "removed.txt",
				annotations: "annotations.zip",
			},
			prefer: true, // Set prefer explicitly on the prebuilt. We will assert that rdep gets source in a test case.
		}
		// rdeps
		java_library {
			name: "mymodule",
			srcs: ["a.java"],
			sdk_version: "current",
			libs: ["sdklib.stubs",], // this should be dynamically resolved to sdklib.stubs (source) or prebuilt_sdklib.stubs (prebuilt)
		}
	`

	fixture := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("sdklib"),
		// We can use any of the apex contribution build flags from build/soong/android/config.go#mainlineApexContributionBuildFlags here
		android.PrepareForTestWithBuildFlag("RELEASE_APEX_CONTRIBUTIONS_ADSERVICES", "my_mainline_module_contributions"),
	)

	result := fixture.RunTestWithBp(t, bp)
	// Make sure that rdeps get the correct source vs prebuilt based on mainline_module_contributions
	public := result.ModuleForTests("mymodule", "android_common")
	rule := public.Output("javac/mymodule.jar")
	inputs := rule.Implicits.Strings()
	android.AssertStringListContains(t, "Could not find the expected stub on classpath", inputs, "out/soong/.intermediates/sdklib.stubs/android_common/turbine-combined/sdklib.stubs.jar")
}

// test that rdep gets resolved to the correct version of a java_sdk_library (source or a specific prebuilt)
func TestMultipleSdkLibraryPrebuilts(t *testing.T) {
	bp := `
		apex_contributions {
			name: "my_mainline_module_contributions",
			api_domain: "my_mainline_module",
			contents: ["%s"],
		}
		java_sdk_library {
			name: "sdklib",
			srcs: ["a.java"],
			sdk_version: "none",
			system_modules: "none",
			public: {
				enabled: true,
			},
		}
		java_sdk_library_import {
			name: "sdklib.v1", //prebuilt
			source_module_name: "sdklib",
			public: {
				jars: ["a.jar"],
				stub_srcs: ["a.java"],
				current_api: "current.txt",
				removed_api: "removed.txt",
				annotations: "annotations.zip",
			},
		}
		java_sdk_library_import {
			name: "sdklib.v2", //prebuilt
			source_module_name: "sdklib",
			public: {
				jars: ["a.jar"],
				stub_srcs: ["a.java"],
				current_api: "current.txt",
				removed_api: "removed.txt",
				annotations: "annotations.zip",
			},
		}
		// rdeps
		java_library {
			name: "mymodule",
			srcs: ["a.java"],
			libs: ["sdklib.stubs",],
		}
	`
	testCases := []struct {
		desc                   string
		selectedDependencyName string
		expectedStubPath       string
	}{
		{
			desc:                   "Source library is selected using apex_contributions",
			selectedDependencyName: "sdklib",
			expectedStubPath:       "out/soong/.intermediates/sdklib.stubs/android_common/turbine-combined/sdklib.stubs.jar",
		},
		{
			desc:                   "Prebuilt library v1 is selected using apex_contributions",
			selectedDependencyName: "prebuilt_sdklib.v1",
			expectedStubPath:       "out/soong/.intermediates/prebuilt_sdklib.v1.stubs/android_common/combined/sdklib.stubs.jar",
		},
		{
			desc:                   "Prebuilt library v2 is selected using apex_contributions",
			selectedDependencyName: "prebuilt_sdklib.v2",
			expectedStubPath:       "out/soong/.intermediates/prebuilt_sdklib.v2.stubs/android_common/combined/sdklib.stubs.jar",
		},
	}

	fixture := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("sdklib", "sdklib.v1", "sdklib.v2"),
		android.PrepareForTestWithBuildFlag("RELEASE_APEX_CONTRIBUTIONS_ADSERVICES", "my_mainline_module_contributions"),
	)

	for _, tc := range testCases {
		result := fixture.RunTestWithBp(t, fmt.Sprintf(bp, tc.selectedDependencyName))

		// Make sure that rdeps get the correct source vs prebuilt based on mainline_module_contributions
		public := result.ModuleForTests("mymodule", "android_common")
		rule := public.Output("javac/mymodule.jar")
		inputs := rule.Implicits.Strings()
		android.AssertStringListContains(t, "Could not find the expected stub on classpath", inputs, tc.expectedStubPath)
	}
}

func TestStubLinkType(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo"),
	).ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(
		`module "baz" variant "android_common": compiles against system API, but dependency `+
			`"bar.stubs.system" is compiling against module API. In order to fix this, `+
			`consider adjusting sdk_version: OR platform_apis: property of the source or `+
			`target module so that target module is built with the same or smaller API set `+
			`when compared to the source.`),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java"],
			sdk_version: "current",
		}
		java_library {
			name: "bar.stubs.system",
			srcs: ["a.java"],
			sdk_version: "module_current",
			is_stubs_module: false,
		}

		java_library {
			name: "baz",
			srcs: ["b.java"],
			libs: [
				"foo.stubs.system",
				"bar.stubs.system",
			],
			sdk_version: "system_current",
		}
		`)
}

func TestSdkLibDirectDependency(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("foo", "bar"),
	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern([]string{
		`module "baz" variant "android_common": cannot depend directly on java_sdk_library ` +
			`"foo"; try depending on "foo.stubs", or "foo.impl" instead`,
		`module "baz" variant "android_common": cannot depend directly on java_sdk_library ` +
			`"prebuilt_bar"; try depending on "bar.stubs", or "bar.impl" instead`,
	}),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java"],
			sdk_version: "current",
			public: {
				enabled: true,
			},
		}

		java_sdk_library_import {
			name: "foo",
			public: {
				jars: ["a.jar"],
				stub_srcs: ["a.java"],
				current_api: "current.txt",
				removed_api: "removed.txt",
				annotations: "annotations.zip",
			},
		}

		java_sdk_library {
			name: "bar",
			srcs: ["a.java"],
			sdk_version: "current",
			public: {
				enabled: true,
			},
		}

		java_sdk_library_import {
			name: "bar",
			prefer: true,
			public: {
				jars: ["a.jar"],
				stub_srcs: ["a.java"],
				current_api: "current.txt",
				removed_api: "removed.txt",
				annotations: "annotations.zip",
			},
		}

		java_library {
			name: "baz",
			srcs: ["b.java"],
			libs: [
				"foo",
				"bar",
			],
		}
	`)
}

func TestSdkLibDirectDependencyWithPrebuiltSdk(t *testing.T) {
	android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
			variables.Platform_sdk_version = intPtr(34)
			variables.Platform_sdk_codename = stringPtr("VanillaIceCream")
			variables.Platform_version_active_codenames = []string{"VanillaIceCream"}
			variables.Platform_systemsdk_versions = []string{"33", "34", "VanillaIceCream"}
			variables.DeviceSystemSdkVersions = []string{"VanillaIceCream"}
		}),
		FixtureWithPrebuiltApis(map[string][]string{
			"33": {"foo"},
			"34": {"foo"},
			"35": {"foo"},
		}),
	).ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(
		`module "baz" variant "android_common": cannot depend directly on java_sdk_library "foo"; `+
			`try depending on "sdk_public_33_foo", "sdk_system_33_foo", "sdk_test_33_foo", `+
			`"sdk_module-lib_33_foo", or "sdk_system-server_33_foo" instead`),
	).RunTestWithBp(t, `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java"],
			sdk_version: "current",
			public: {
				enabled: true,
			},
			system: {
				enabled: true,
			},
		}

		java_library {
			name: "baz",
			srcs: ["b.java"],
			libs: [
				"foo",
			],
			sdk_version: "system_33",
		}
	`)
}
