// Copyright 2021 The Android Open Source Project
//
// 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 rust

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

	"android/soong/android"
	"android/soong/cc"
)

func TestVendorSnapshotCapture(t *testing.T) {
	bp := `
	rust_ffi {
		name: "libffivendor_available",
		crate_name: "ffivendor_available",
		srcs: ["lib.rs"],
		vendor_available: true,
		include_dirs: ["rust_headers/"],
	}

	rust_ffi {
		name: "libffivendor",
		crate_name: "ffivendor",
		srcs: ["lib.rs"],
		vendor: true,
		include_dirs: ["rust_headers/"],
	}

	rust_library {
		name: "librustvendor_available",
		crate_name: "rustvendor_available",
		srcs: ["lib.rs"],
		vendor_available: true,
		include_dirs: ["rust_headers/"],
	}

	rust_library_rlib {
		name: "librustvendor",
		crate_name: "rustvendor",
		srcs: ["lib.rs"],
		vendor: true,
		include_dirs: ["rust_headers/"],
	}

	rust_binary {
		name: "vendor_available_bin",
		vendor_available: true,
		srcs: ["srcs/lib.rs"],
	}

	rust_binary {
		name: "vendor_bin",
		vendor: true,
		srcs: ["srcs/lib.rs"],
	}
    `
	skipTestIfOsNotSupported(t)
	result := android.GroupFixturePreparers(
		prepareForRustTest,
		rustMockedFiles.AddToFixture(),
		android.FixtureModifyProductVariables(
			func(variables android.FixtureProductVariables) {
				variables.DeviceVndkVersion = StringPtr("current")
				variables.Platform_vndk_version = StringPtr("29")
			},
		),
	).RunTestWithBp(t, bp)
	ctx := result.TestContext

	// Check Vendor snapshot output.

	snapshotDir := "vendor-snapshot"
	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
	snapshotSingleton := ctx.SingletonForTests("vendor-snapshot")
	var jsonFiles []string
	for _, arch := range [][]string{
		[]string{"arm64", "armv8-a"},
		[]string{"arm", "armv7-a-neon"},
	} {
		archType := arch[0]
		archVariant := arch[1]
		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)

		// For shared libraries, only non-VNDK vendor_available modules are captured
		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libffivendor_available", "libffivendor_available.so", sharedDir, sharedVariant)
		jsonFiles = append(jsonFiles,
			filepath.Join(sharedDir, "libffivendor_available.so.json"))

		// For static libraries, all vendor:true and vendor_available modules (including VNDK) are captured.
		staticVariant := fmt.Sprintf("android_vendor.29_%s_%s_static", archType, archVariant)
		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libffivendor_available", "libffivendor_available.a", staticDir, staticVariant)
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libffivendor", "libffivendor.a", staticDir, staticVariant)
		jsonFiles = append(jsonFiles,
			filepath.Join(staticDir, "libffivendor_available.a.json"))
		jsonFiles = append(jsonFiles,
			filepath.Join(staticDir, "libffivendor.a.json"))

		// For rlib libraries, all vendor:true and vendor_available modules (including VNDK) are captured.
		rlibVariant := fmt.Sprintf("android_vendor.29_%s_%s_rlib_rlib-std", archType, archVariant)
		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librustvendor_available", "librustvendor_available.rlib", rlibDir, rlibVariant)
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librustvendor", "librustvendor.rlib", rlibDir, rlibVariant)
		jsonFiles = append(jsonFiles,
			filepath.Join(rlibDir, "librustvendor_available.rlib.json"))
		jsonFiles = append(jsonFiles,
			filepath.Join(rlibDir, "librustvendor.rlib.json"))

		// For binary executables, all vendor:true and vendor_available modules are captured.
		if archType == "arm64" {
			binaryVariant := fmt.Sprintf("android_vendor.29_%s_%s", archType, archVariant)
			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
			cc.CheckSnapshot(t, ctx, snapshotSingleton, "vendor_available_bin", "vendor_available_bin", binaryDir, binaryVariant)
			cc.CheckSnapshot(t, ctx, snapshotSingleton, "vendor_bin", "vendor_bin", binaryDir, binaryVariant)
			jsonFiles = append(jsonFiles,
				filepath.Join(binaryDir, "vendor_available_bin.json"))
			jsonFiles = append(jsonFiles,
				filepath.Join(binaryDir, "vendor_bin.json"))
		}
	}

	for _, jsonFile := range jsonFiles {
		// verify all json files exist
		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
			t.Errorf("%q expected but not found; #%v", jsonFile, jsonFiles)
		}
	}

	// fake snapshot should have all outputs in the normal snapshot.
	fakeSnapshotSingleton := ctx.SingletonForTests("vendor-fake-snapshot")

	for _, output := range snapshotSingleton.AllOutputs() {
		fakeOutput := strings.Replace(output, "/vendor-snapshot/", "/fake/vendor-snapshot/", 1)
		if fakeSnapshotSingleton.MaybeOutput(fakeOutput).Rule == nil {
			t.Errorf("%q expected but not found", fakeOutput)
		}
	}
}

func TestVendorSnapshotDirected(t *testing.T) {
	bp := `
	rust_ffi_shared {
		name: "libffivendor_available",
		crate_name: "ffivendor_available",
		srcs: ["lib.rs"],
		vendor_available: true,
	}

	rust_library {
		name: "librustvendor_available",
		crate_name: "rustvendor_available",
		srcs: ["lib.rs"],
		vendor_available: true,
	}

	rust_ffi_shared {
		name: "libffivendor_exclude",
		crate_name: "ffivendor_exclude",
		srcs: ["lib.rs"],
		vendor_available: true,
	}

	rust_library {
		name: "librustvendor_exclude",
		crate_name: "rustvendor_exclude",
		srcs: ["lib.rs"],
		vendor_available: true,
	}
`
	ctx := testRustVndk(t, bp)
	ctx.Config().TestProductVariables.VendorSnapshotModules = make(map[string]bool)
	ctx.Config().TestProductVariables.VendorSnapshotModules["librustvendor_available"] = true
	ctx.Config().TestProductVariables.VendorSnapshotModules["libffivendor_available"] = true
	ctx.Config().TestProductVariables.DirectedVendorSnapshot = true

	// Check Vendor snapshot output.

	snapshotDir := "vendor-snapshot"
	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
	snapshotSingleton := ctx.SingletonForTests("vendor-snapshot")

	var includeJsonFiles []string

	for _, arch := range [][]string{
		[]string{"arm64", "armv8-a"},
		[]string{"arm", "armv7-a-neon"},
	} {
		archType := arch[0]
		archVariant := arch[1]
		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)

		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
		rlibVariant := fmt.Sprintf("android_vendor.29_%s_%s_rlib_rlib-std", archType, archVariant)
		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")

		// Included modules
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librustvendor_available", "librustvendor_available.rlib", rlibDir, rlibVariant)
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libffivendor_available", "libffivendor_available.so", sharedDir, sharedVariant)
		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librustvendor_available.rlib.json"))
		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libffivendor_available.so.json"))

		// Excluded modules. Modules not included in the directed vendor snapshot
		// are still include as fake modules.
		cc.CheckSnapshotRule(t, ctx, snapshotSingleton, "librustvendor_exclude", "librustvendor_exclude.rlib", rlibDir, rlibVariant)
		cc.CheckSnapshotRule(t, ctx, snapshotSingleton, "libffivendor_exclude", "libffivendor_exclude.so", sharedDir, sharedVariant)
		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librustvendor_exclude.rlib.json"))
		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libffivendor_exclude.so.json"))
	}

	// Verify that each json file for an included module has a rule.
	for _, jsonFile := range includeJsonFiles {
		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
			t.Errorf("include json file %q not found", jsonFile)
		}
	}
}

func TestVendorSnapshotExclude(t *testing.T) {

	// This test verifies that the exclude_from_vendor_snapshot property
	// makes its way from the Android.bp source file into the module data
	// structure. It also verifies that modules are correctly included or
	// excluded in the vendor snapshot based on their path (framework or
	// vendor) and the exclude_from_vendor_snapshot property.

	frameworkBp := `
		rust_ffi_shared {
			name: "libinclude",
			crate_name: "include",
			srcs: ["include.rs"],
			vendor_available: true,
		}

		rust_ffi_shared {
			name: "libexclude",
			crate_name: "exclude",
			srcs: ["exclude.rs"],
			vendor: true,
			exclude_from_vendor_snapshot: true,
		}

		rust_ffi_shared {
			name: "libavailable_exclude",
			crate_name: "available_exclude",
			srcs: ["lib.rs"],
			vendor_available: true,
			exclude_from_vendor_snapshot: true,
		}

		rust_library {
			name: "librust_include",
			crate_name: "rust_include",
			srcs: ["include.rs"],
			vendor_available: true,
		}

		rust_library_rlib {
			name: "librust_exclude",
			crate_name: "rust_exclude",
			srcs: ["exclude.rs"],
			vendor: true,
			exclude_from_vendor_snapshot: true,
		}

		rust_library {
			name: "librust_available_exclude",
			crate_name: "rust_available_exclude",
			srcs: ["lib.rs"],
			vendor_available: true,
			exclude_from_vendor_snapshot: true,
		}
	`

	mockFS := map[string][]byte{
		"framework/Android.bp": []byte(frameworkBp),
		"framework/include.rs": nil,
		"framework/exclude.rs": nil,
	}

	ctx := testRustVndkFs(t, "", mockFS)

	// Test an include and exclude framework module.
	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "libinclude", false, sharedVendorVariant)
	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "libexclude", true, sharedVendorVariant)
	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "libavailable_exclude", true, sharedVendorVariant)

	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "librust_include", false, rlibVendorVariant)
	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "librust_exclude", true, rlibVendorVariant)
	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "librust_available_exclude", true, rlibVendorVariant)

	// Verify the content of the vendor snapshot.

	snapshotDir := "vendor-snapshot"
	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
	snapshotSingleton := ctx.SingletonForTests("vendor-snapshot")

	var includeJsonFiles []string
	var excludeJsonFiles []string

	for _, arch := range [][]string{
		[]string{"arm64", "armv8-a"},
		[]string{"arm", "armv7-a-neon"},
	} {
		archType := arch[0]
		archVariant := arch[1]
		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)

		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
		rlibVariant := fmt.Sprintf("android_vendor.29_%s_%s_rlib_rlib-std", archType, archVariant)
		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")

		// Included modules
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libinclude.so.json"))
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librust_include", "librust_include.rlib", rlibDir, rlibVariant)
		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librust_include.rlib.json"))

		// Excluded modules
		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libexclude.so.json"))
		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libavailable_exclude.so.json"))
		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "librust_exclude", "librust_exclude.rlib", rlibDir, rlibVariant)
		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "librust_exclude.rlib.json"))
		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "librust_available_exclude", "librust_available_exclude.rlib", rlibDir, rlibVariant)
		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "librust_available_exclude.rlib.json"))
	}

	// Verify that each json file for an included module has a rule.
	for _, jsonFile := range includeJsonFiles {
		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
			t.Errorf("include json file %q not found", jsonFile)
		}
	}

	// Verify that each json file for an excluded module has no rule.
	for _, jsonFile := range excludeJsonFiles {
		if snapshotSingleton.MaybeOutput(jsonFile).Rule != nil {
			t.Errorf("exclude json file %q found", jsonFile)
		}
	}
}

func TestVendorSnapshotUse(t *testing.T) {
	frameworkBp := `
	cc_library {
		name: "libvndk",
		vendor_available: true,
		product_available: true,
		vndk: {
			enabled: true,
		},
		nocrt: true,
	}

	cc_library {
		name: "libvendor",
		vendor: true,
		nocrt: true,
		no_libcrt: true,
		stl: "none",
		system_shared_libs: [],
	}

	cc_library {
		name: "libvendor_available",
		vendor_available: true,
		nocrt: true,
		no_libcrt: true,
		stl: "none",
		system_shared_libs: [],
	}

	cc_library {
		name: "lib32",
		vendor: true,
		nocrt: true,
		no_libcrt: true,
		stl: "none",
		system_shared_libs: [],
		compile_multilib: "32",
	}

	cc_library {
		name: "lib64",
		vendor: true,
		nocrt: true,
		no_libcrt: true,
		stl: "none",
		system_shared_libs: [],
		compile_multilib: "64",
	}

	rust_binary {
		name: "bin",
		vendor: true,
		srcs: ["bin.rs"],
	}

	rust_binary {
		name: "bin32",
		vendor: true,
		compile_multilib: "32",
		srcs: ["bin.rs"],
	}
`

	vndkBp := `
	vndk_prebuilt_shared {
		name: "libvndk",
		version: "30",
		target_arch: "arm64",
		vendor_available: true,
		product_available: true,
		vndk: {
			enabled: true,
		},
		arch: {
			arm64: {
				srcs: ["libvndk.so"],
				export_include_dirs: ["include/libvndk"],
			},
			arm: {
				srcs: ["libvndk.so"],
				export_include_dirs: ["include/libvndk"],
			},
		},
	}

	// old snapshot module which has to be ignored
	vndk_prebuilt_shared {
		name: "libvndk",
		version: "26",
		target_arch: "arm64",
		vendor_available: true,
		product_available: true,
		vndk: {
			enabled: true,
		},
		arch: {
			arm64: {
				srcs: ["libvndk.so"],
				export_include_dirs: ["include/libvndk"],
			},
			arm: {
				srcs: ["libvndk.so"],
				export_include_dirs: ["include/libvndk"],
			},
		},
	}

	// different arch snapshot which has to be ignored
	vndk_prebuilt_shared {
		name: "libvndk",
		version: "30",
		target_arch: "arm",
		vendor_available: true,
		product_available: true,
		vndk: {
			enabled: true,
		},
		arch: {
			arm: {
				srcs: ["libvndk.so"],
				export_include_dirs: ["include/libvndk"],
			},
		},
	}
`

	vendorProprietaryBp := `
	cc_library {
		name: "libvendor_without_snapshot",
		vendor: true,
		nocrt: true,
		no_libcrt: true,
		stl: "none",
		system_shared_libs: [],
	}

	rust_library {
		name: "librust_vendor_available",
		crate_name: "rust_vendor",
		vendor_available: true,
		srcs: ["client.rs"],
	}

	rust_ffi_shared {
		name: "libclient",
		crate_name: "client",
		vendor: true,
		shared_libs: ["libvndk", "libvendor_available"],
		static_libs: ["libvendor", "libvendor_without_snapshot"],
		rustlibs: ["librust_vendor_available"],
		arch: {
			arm64: {
				shared_libs: ["lib64"],
			},
			arm: {
				shared_libs: ["lib32"],
			},
		},
		srcs: ["client.rs"],
	}

	rust_library_rlib {
		name: "libclient_rust",
		crate_name: "client_rust",
		vendor: true,
		shared_libs: ["libvndk", "libvendor_available"],
		static_libs: ["libvendor", "libvendor_without_snapshot"],
		rustlibs: ["librust_vendor_available"],
		arch: {
			arm64: {
				shared_libs: ["lib64"],
			},
			arm: {
				shared_libs: ["lib32"],
			},
		},
		srcs: ["client.rs"],
	}

	rust_binary {
		name: "bin_without_snapshot",
		vendor: true,
		static_libs: ["libvndk"],
		srcs: ["bin.rs"],
		rustlibs: ["librust_vendor_available"],
	}

	vendor_snapshot {
		name: "vendor_snapshot",
		version: "30",
		arch: {
			arm64: {
				vndk_libs: [
					"libvndk",
				],
				static_libs: [
					"libvendor",
					"libvndk",
					"libclang_rt.builtins",
					"note_memtag_heap_sync",
				],
				shared_libs: [
					"libvendor_available",
					"lib64",
				],
				rlibs: [
					"libstd",
					"librust_vendor_available",
				],
				binaries: [
					"bin",
				],
                objects: [
				    "crtend_so",
					"crtbegin_so",
					"crtbegin_dynamic",
					"crtend_android"
				],
			},
			arm: {
				vndk_libs: [
					"libvndk",
				],
				static_libs: [
					"libvendor",
					"libvndk",
					"libclang_rt.builtins",
				],
				shared_libs: [
					"libvendor_available",
					"lib32",
				],
				rlibs: [
					"libstd",
					"librust_vendor_available",
				],
				binaries: [
					"bin32",
				],
                objects: [
				    "crtend_so",
					"crtbegin_so",
					"crtbegin_dynamic",
					"crtend_android"
				],

			},
		}
	}

	vendor_snapshot_object {
		name: "crtend_so",
		version: "30",
		target_arch: "arm64",
		vendor: true,
		stl: "none",
		crt: true,
		arch: {
			arm64: {
				src: "crtend_so.o",
			},
			arm: {
				src: "crtend_so.o",
			},
		},
	}

	vendor_snapshot_object {
		name: "crtbegin_so",
		version: "30",
		target_arch: "arm64",
		vendor: true,
		stl: "none",
		crt: true,
		arch: {
			arm64: {
				src: "crtbegin_so.o",
			},
			arm: {
				src: "crtbegin_so.o",
			},
		},
	}

	vendor_snapshot_rlib {
		name: "libstd",
		version: "30",
		target_arch: "arm64",
		vendor: true,
		sysroot: true,
		arch: {
			arm64: {
				src: "libstd.rlib",
			},
			arm: {
				src: "libstd.rlib",
			},
		},
	}

	vendor_snapshot_rlib {
		name: "librust_vendor_available",
		version: "30",
		target_arch: "arm64",
		vendor: true,
		arch: {
			arm64: {
				src: "librust_vendor_available.rlib",
			},
			arm: {
				src: "librust_vendor_available.rlib",
			},
		},
	}

	vendor_snapshot_object {
		name: "crtend_android",
		version: "30",
		target_arch: "arm64",
		vendor: true,
		stl: "none",
		crt: true,
		arch: {
			arm64: {
				src: "crtend_so.o",
			},
			arm: {
				src: "crtend_so.o",
			},
		},
	}

	vendor_snapshot_object {
		name: "crtbegin_dynamic",
		version: "30",
		target_arch: "arm64",
		vendor: true,
		stl: "none",
		crt: true,
		arch: {
			arm64: {
				src: "crtbegin_so.o",
			},
			arm: {
				src: "crtbegin_so.o",
			},
		},
	}

	vendor_snapshot_static {
		name: "libvndk",
		version: "30",
		target_arch: "arm64",
		compile_multilib: "both",
		vendor: true,
		arch: {
			arm64: {
				src: "libvndk.a",
			},
			arm: {
				src: "libvndk.a",
			},
		},
		shared_libs: ["libvndk"],
		export_shared_lib_headers: ["libvndk"],
	}

	vendor_snapshot_static {
		name: "libclang_rt.builtins",
		version: "30",
		target_arch: "arm64",
		vendor: true,
		arch: {
			arm: {
				src: "libclang_rt.builtins-arm-android.a",
			},
			arm64: {
				src: "libclang_rt.builtins-aarch64-android.a",
			},
		},
    }

	vendor_snapshot_shared {
		name: "lib32",
		version: "30",
		target_arch: "arm64",
		compile_multilib: "32",
		vendor: true,
		arch: {
			arm: {
				src: "lib32.so",
			},
		},
	}

	vendor_snapshot_shared {
		name: "lib64",
		version: "30",
		target_arch: "arm64",
		compile_multilib: "64",
		vendor: true,
		arch: {
			arm64: {
				src: "lib64.so",
			},
		},
	}
	vendor_snapshot_shared {
		name: "liblog",
		version: "30",
		target_arch: "arm64",
		compile_multilib: "64",
		vendor: true,
		arch: {
			arm64: {
				src: "liblog.so",
			},
		},
	}

	vendor_snapshot_static {
		name: "libvendor",
		version: "30",
		target_arch: "arm64",
		compile_multilib: "both",
		vendor: true,
		arch: {
			arm64: {
				src: "libvendor.a",
				export_include_dirs: ["include/libvendor"],
			},
			arm: {
				src: "libvendor.a",
				export_include_dirs: ["include/libvendor"],
			},
		},
	}

	vendor_snapshot_shared {
		name: "libvendor_available",
		version: "30",
		target_arch: "arm64",
		compile_multilib: "both",
		vendor: true,
		arch: {
			arm64: {
				src: "libvendor_available.so",
				export_include_dirs: ["include/libvendor"],
			},
			arm: {
				src: "libvendor_available.so",
				export_include_dirs: ["include/libvendor"],
			},
		},
	}

	vendor_snapshot_binary {
		name: "bin",
		version: "30",
		target_arch: "arm64",
		compile_multilib: "64",
		vendor: true,
		arch: {
			arm64: {
				src: "bin",
			},
		},
	}

	vendor_snapshot_binary {
		name: "bin32",
		version: "30",
		target_arch: "arm64",
		compile_multilib: "32",
		vendor: true,
		arch: {
			arm: {
				src: "bin32",
			},
		},
	}

	// Test sanitizers use the snapshot libraries
	rust_binary {
		name: "memtag_binary",
		srcs: ["vendor/bin.rs"],
		vendor: true,
		compile_multilib: "64",
		sanitize: {
			memtag_heap: true,
			diag: {
				memtag_heap: true,
			}
		},
	}

	// old snapshot module which has to be ignored
	vendor_snapshot_binary {
		name: "bin",
		version: "26",
		target_arch: "arm64",
		compile_multilib: "first",
		vendor: true,
		arch: {
			arm64: {
				src: "bin",
			},
		},
	}

	// different arch snapshot which has to be ignored
	vendor_snapshot_binary {
		name: "bin",
		version: "30",
		target_arch: "arm",
		compile_multilib: "first",
		vendor: true,
		arch: {
			arm64: {
				src: "bin",
			},
		},
	}

	vendor_snapshot_static {
		name: "note_memtag_heap_sync",
		vendor: true,
		target_arch: "arm64",
		version: "30",
		arch: {
			arm64: {
				src: "note_memtag_heap_sync.a",
			},
		},
	}

`

	mockFS := android.MockFS{
		"framework/Android.bp":                          []byte(frameworkBp),
		"framework/bin.rs":                              nil,
		"note_memtag_heap_sync.a":                       nil,
		"vendor/Android.bp":                             []byte(vendorProprietaryBp),
		"vendor/bin":                                    nil,
		"vendor/bin32":                                  nil,
		"vendor/bin.rs":                                 nil,
		"vendor/client.rs":                              nil,
		"vendor/include/libvndk/a.h":                    nil,
		"vendor/include/libvendor/b.h":                  nil,
		"vendor/libvndk.a":                              nil,
		"vendor/libvendor.a":                            nil,
		"vendor/libvendor.so":                           nil,
		"vendor/lib32.so":                               nil,
		"vendor/lib64.so":                               nil,
		"vendor/liblog.so":                              nil,
		"vendor/libstd.rlib":                            nil,
		"vendor/librust_vendor_available.rlib":          nil,
		"vendor/crtbegin_so.o":                          nil,
		"vendor/crtend_so.o":                            nil,
		"vendor/libclang_rt.builtins-aarch64-android.a": nil,
		"vendor/libclang_rt.builtins-arm-android.a":     nil,
		"vndk/Android.bp":                               []byte(vndkBp),
		"vndk/include/libvndk/a.h":                      nil,
		"vndk/libvndk.so":                               nil,
	}

	sharedVariant := "android_vendor.30_arm64_armv8-a_shared"
	rlibVariant := "android_vendor.30_arm64_armv8-a_rlib_rlib-std"
	staticVariant := "android_vendor.30_arm64_armv8-a_static"
	binaryVariant := "android_vendor.30_arm64_armv8-a"

	shared32Variant := "android_vendor.30_arm_armv7-a-neon_shared"
	binary32Variant := "android_vendor.30_arm_armv7-a-neon"

	ctx := testRustVndkFsVersions(t, "", mockFS, "30", "current", "31")

	// libclient uses libvndk.vndk.30.arm64, libvendor.vendor_static.30.arm64, libvendor_without_snapshot
	libclientLdFlags := ctx.ModuleForTests("libclient", sharedVariant).Rule("rustc").Args["linkFlags"]
	for _, input := range [][]string{
		[]string{sharedVariant, "libvndk.vndk.30.arm64"},
		[]string{staticVariant, "libvendor.vendor_static.30.arm64"},
		[]string{staticVariant, "libvendor_without_snapshot"},
	} {
		outputPaths := cc.GetOutputPaths(ctx, input[0] /* variant */, []string{input[1]} /* module name */)
		if !strings.Contains(libclientLdFlags, outputPaths[0].String()) {
			t.Errorf("libflags for libclient must contain %#v, but was %#v", outputPaths[0], libclientLdFlags)
		}
	}

	libclientAndroidMkSharedLibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkSharedLibs
	if g, w := libclientAndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "lib64", "liblog.vendor", "libc.vendor", "libm.vendor", "libdl.vendor"}; !reflect.DeepEqual(g, w) {
		t.Errorf("wanted libclient AndroidMkSharedLibs %q, got %q", w, g)
	}

	libclientAndroidMkStaticLibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkStaticLibs
	if g, w := libclientAndroidMkStaticLibs, []string{"libvendor", "libvendor_without_snapshot", "libclang_rt.builtins.vendor"}; !reflect.DeepEqual(g, w) {
		t.Errorf("wanted libclient AndroidMkStaticLibs %q, got %q", w, g)
	}

	libclientAndroidMkRlibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkRlibs
	if g, w := libclientAndroidMkRlibs, []string{"librust_vendor_available.vendor_rlib.30.arm64.rlib-std", "libstd.vendor_rlib.30.arm64"}; !reflect.DeepEqual(g, w) {
		t.Errorf("wanted libclient libclientAndroidMkRlibs %q, got %q", w, g)
	}

	libclientAndroidMkDylibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkDylibs
	if len(libclientAndroidMkDylibs) > 0 {
		t.Errorf("wanted libclient libclientAndroidMkDylibs [], got %q", libclientAndroidMkDylibs)
	}

	libclient32AndroidMkSharedLibs := ctx.ModuleForTests("libclient", shared32Variant).Module().(*Module).Properties.AndroidMkSharedLibs
	if g, w := libclient32AndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "lib32", "liblog.vendor", "libc.vendor", "libm.vendor", "libdl.vendor"}; !reflect.DeepEqual(g, w) {
		t.Errorf("wanted libclient32 AndroidMkSharedLibs %q, got %q", w, g)
	}

	libclientRustAndroidMkRlibs := ctx.ModuleForTests("libclient_rust", rlibVariant).Module().(*Module).Properties.AndroidMkRlibs
	if g, w := libclientRustAndroidMkRlibs, []string{"librust_vendor_available.vendor_rlib.30.arm64.rlib-std", "libstd.vendor_rlib.30.arm64"}; !reflect.DeepEqual(g, w) {
		t.Errorf("wanted libclient libclientAndroidMkRlibs %q, got %q", w, g)
	}

	binWithoutSnapshotLdFlags := ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Rule("rustc").Args["linkFlags"]
	libVndkStaticOutputPaths := cc.GetOutputPaths(ctx, staticVariant, []string{"libvndk.vendor_static.30.arm64"})
	if !strings.Contains(binWithoutSnapshotLdFlags, libVndkStaticOutputPaths[0].String()) {
		t.Errorf("libflags for bin_without_snapshot must contain %#v, but was %#v",
			libVndkStaticOutputPaths[0], binWithoutSnapshotLdFlags)
	}

	// bin is installed by bin.vendor_binary.30.arm64
	ctx.ModuleForTests("bin.vendor_binary.30.arm64", binaryVariant).Output("bin")

	// bin32 is installed by bin32.vendor_binary.30.arm64
	ctx.ModuleForTests("bin32.vendor_binary.30.arm64", binary32Variant).Output("bin32")

	// bin_without_snapshot is installed by bin_without_snapshot
	ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Output("bin_without_snapshot")

	// libvendor, libvendor_available and bin don't have vendor.30 variant
	libvendorVariants := ctx.ModuleVariantsForTests("libvendor")
	if android.InList(sharedVariant, libvendorVariants) {
		t.Errorf("libvendor must not have variant %#v, but it does", sharedVariant)
	}

	libvendorAvailableVariants := ctx.ModuleVariantsForTests("libvendor_available")
	if android.InList(sharedVariant, libvendorAvailableVariants) {
		t.Errorf("libvendor_available must not have variant %#v, but it does", sharedVariant)
	}

	binVariants := ctx.ModuleVariantsForTests("bin")
	if android.InList(binaryVariant, binVariants) {
		t.Errorf("bin must not have variant %#v, but it does", sharedVariant)
	}

	memtagStaticLibs := ctx.ModuleForTests("memtag_binary", "android_vendor.30_arm64_armv8-a").Module().(*Module).Properties.AndroidMkStaticLibs
	if g, w := memtagStaticLibs, []string{"libclang_rt.builtins.vendor", "note_memtag_heap_sync.vendor"}; !reflect.DeepEqual(g, w) {
		t.Errorf("wanted memtag_binary AndroidMkStaticLibs %q, got %q", w, g)
	}
}

func TestRecoverySnapshotCapture(t *testing.T) {
	bp := `
	rust_ffi {
		name: "librecovery",
		recovery: true,
		srcs: ["foo.rs"],
		crate_name: "recovery",
	}

	rust_ffi {
		name: "librecovery_available",
		recovery_available: true,
		srcs: ["foo.rs"],
		crate_name: "recovery_available",
	}

	rust_library_rlib {
		name: "librecovery_rlib",
		recovery: true,
		srcs: ["foo.rs"],
		crate_name: "recovery_rlib",
	}

	rust_library_rlib {
		name: "librecovery_available_rlib",
		recovery_available: true,
		srcs: ["foo.rs"],
		crate_name: "recovery_available_rlib",
	}

	rust_binary {
		name: "recovery_bin",
		recovery: true,
		srcs: ["foo.rs"],
	}

	rust_binary {
		name: "recovery_available_bin",
		recovery_available: true,
		srcs: ["foo.rs"],
	}

`
	// Check Recovery snapshot output.

	ctx := testRustRecoveryFsVersions(t, bp, rustMockedFiles, "", "29", "current")
	snapshotDir := "recovery-snapshot"
	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")

	var jsonFiles []string

	for _, arch := range [][]string{
		[]string{"arm64", "armv8-a"},
	} {
		archType := arch[0]
		archVariant := arch[1]
		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)

		// For shared libraries, all recovery:true and recovery_available modules are captured.
		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
		jsonFiles = append(jsonFiles,
			filepath.Join(sharedDir, "librecovery.so.json"),
			filepath.Join(sharedDir, "librecovery_available.so.json"))

		// For static libraries, all recovery:true and recovery_available modules are captured.
		staticVariant := fmt.Sprintf("android_recovery_%s_%s_static", archType, archVariant)
		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.a", staticDir, staticVariant)
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.a", staticDir, staticVariant)
		jsonFiles = append(jsonFiles,
			filepath.Join(staticDir, "librecovery.a.json"),
			filepath.Join(staticDir, "librecovery_available.a.json"))

		// For rlib libraries, all recovery:true and recovery_available modules are captured.
		rlibVariant := fmt.Sprintf("android_recovery_%s_%s_rlib_rlib-std", archType, archVariant)
		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_rlib", "librecovery_rlib.rlib", rlibDir, rlibVariant)
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available_rlib", "librecovery_available_rlib.rlib", rlibDir, rlibVariant)
		jsonFiles = append(jsonFiles,
			filepath.Join(rlibDir, "librecovery_rlib.rlib.json"),
			filepath.Join(rlibDir, "librecovery_available_rlib.rlib.json"))

		// For binary executables, all recovery:true and recovery_available modules are captured.
		if archType == "arm64" {
			binaryVariant := fmt.Sprintf("android_recovery_%s_%s", archType, archVariant)
			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
			cc.CheckSnapshot(t, ctx, snapshotSingleton, "recovery_bin", "recovery_bin", binaryDir, binaryVariant)
			cc.CheckSnapshot(t, ctx, snapshotSingleton, "recovery_available_bin", "recovery_available_bin", binaryDir, binaryVariant)
			jsonFiles = append(jsonFiles,
				filepath.Join(binaryDir, "recovery_bin.json"),
				filepath.Join(binaryDir, "recovery_available_bin.json"))
		}
	}

	for _, jsonFile := range jsonFiles {
		// verify all json files exist
		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
			t.Errorf("%q expected but not found", jsonFile)
		}
	}
}

func TestRecoverySnapshotExclude(t *testing.T) {
	// This test verifies that the exclude_from_recovery_snapshot property
	// makes its way from the Android.bp source file into the module data
	// structure. It also verifies that modules are correctly included or
	// excluded in the recovery snapshot based on their path (framework or
	// vendor) and the exclude_from_recovery_snapshot property.

	frameworkBp := `
		rust_ffi_shared {
			name: "libinclude",
			srcs: ["src/include.rs"],
			recovery_available: true,
			crate_name: "include",
		}
		rust_ffi_shared {
			name: "libexclude",
			srcs: ["src/exclude.rs"],
			recovery: true,
			exclude_from_recovery_snapshot: true,
			crate_name: "exclude",
		}
		rust_ffi_shared {
			name: "libavailable_exclude",
			srcs: ["src/exclude.rs"],
			recovery_available: true,
			exclude_from_recovery_snapshot: true,
			crate_name: "available_exclude",
		}
		rust_library_rlib {
			name: "libinclude_rlib",
			srcs: ["src/include.rs"],
			recovery_available: true,
			crate_name: "include_rlib",
		}
		rust_library_rlib {
			name: "libexclude_rlib",
			srcs: ["src/exclude.rs"],
			recovery: true,
			exclude_from_recovery_snapshot: true,
			crate_name: "exclude_rlib",
		}
		rust_library_rlib {
			name: "libavailable_exclude_rlib",
			srcs: ["src/exclude.rs"],
			recovery_available: true,
			exclude_from_recovery_snapshot: true,
			crate_name: "available_exclude_rlib",
		}
	`

	vendorProprietaryBp := `
		rust_ffi_shared {
			name: "librecovery",
			srcs: ["recovery.rs"],
			recovery: true,
			crate_name: "recovery",
		}
		rust_library_rlib {
			name: "librecovery_rlib",
			srcs: ["recovery.rs"],
			recovery: true,
			crate_name: "recovery_rlib",
		}
	`

	mockFS := map[string][]byte{
		"framework/Android.bp": []byte(frameworkBp),
		"framework/include.rs": nil,
		"framework/exclude.rs": nil,
		"device/Android.bp":    []byte(vendorProprietaryBp),
		"device/recovery.rs":   nil,
	}

	ctx := testRustRecoveryFsVersions(t, "", mockFS, "", "29", "current")

	// Test an include and exclude framework module.
	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libinclude", false, sharedRecoveryVariant)
	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libexclude", true, sharedRecoveryVariant)
	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libavailable_exclude", true, sharedRecoveryVariant)
	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libinclude_rlib", false, rlibRecoveryVariant)
	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libexclude_rlib", true, rlibRecoveryVariant)
	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libavailable_exclude_rlib", true, rlibRecoveryVariant)

	// A recovery module is excluded, but by its path not the exclude_from_recovery_snapshot property
	// ('device/' and 'vendor/' are default excluded). See snapshot/recovery_snapshot.go for more detail.
	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "librecovery", false, sharedRecoveryVariant)
	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "librecovery_rlib", false, rlibRecoveryVariant)

	// Verify the content of the recovery snapshot.

	snapshotDir := "recovery-snapshot"
	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")

	var includeJsonFiles []string
	var excludeJsonFiles []string

	for _, arch := range [][]string{
		[]string{"arm64", "armv8-a"},
	} {
		archType := arch[0]
		archVariant := arch[1]
		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)

		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
		rlibVariant := fmt.Sprintf("android_recovery_%s_%s_rlib_rlib-std", archType, archVariant)
		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")

		// Included modules
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libinclude.so.json"))
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libinclude_rlib", "libinclude_rlib.rlib", rlibDir, rlibVariant)
		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "libinclude_rlib.rlib.json"))

		// Excluded modules
		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libexclude.so.json"))
		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "librecovery.so.json"))
		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libavailable_exclude.so.json"))
		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude_rlib", "libexclude_rlib.rlib", rlibDir, rlibVariant)
		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "libexclude_rlib.rlib.json"))
		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "librecovery_rlib", "librecovery_rlib.rlib", rlibDir, rlibVariant)
		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "librecovery_rlib.rlib.json"))
		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude_rlib", "libavailable_exclude_rlib.rlib", rlibDir, rlibVariant)
		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "libavailable_exclude_rlib.rlib.json"))
	}

	// Verify that each json file for an included module has a rule.
	for _, jsonFile := range includeJsonFiles {
		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
			t.Errorf("include json file %q not found", jsonFile)
		}
	}

	// Verify that each json file for an excluded module has no rule.
	for _, jsonFile := range excludeJsonFiles {
		if snapshotSingleton.MaybeOutput(jsonFile).Rule != nil {
			t.Errorf("exclude json file %q found", jsonFile)
		}
	}
}

func TestRecoverySnapshotDirected(t *testing.T) {
	bp := `
	rust_ffi_shared {
		name: "librecovery",
		recovery: true,
		crate_name: "recovery",
		srcs: ["foo.rs"],
	}

	rust_ffi_shared {
		name: "librecovery_available",
		recovery_available: true,
		crate_name: "recovery_available",
		srcs: ["foo.rs"],
	}

	rust_library_rlib {
		name: "librecovery_rlib",
		recovery: true,
		crate_name: "recovery",
		srcs: ["foo.rs"],
	}

	rust_library_rlib {
		name: "librecovery_available_rlib",
		recovery_available: true,
		crate_name: "recovery_available",
		srcs: ["foo.rs"],
	}

	/* TODO: Uncomment when Rust supports the "prefer" property for prebuilts
	rust_library_rlib {
		name: "libfoo_rlib",
		recovery: true,
		crate_name: "foo",
	}

	rust_prebuilt_rlib {
		name: "libfoo_rlib",
		recovery: true,
		prefer: true,
		srcs: ["libfoo.rlib"],
		crate_name: "foo",
	}
	*/
`
	ctx := testRustRecoveryFsVersions(t, bp, rustMockedFiles, "current", "29", "current")
	ctx.Config().TestProductVariables.RecoverySnapshotModules = make(map[string]bool)
	ctx.Config().TestProductVariables.RecoverySnapshotModules["librecovery"] = true
	ctx.Config().TestProductVariables.RecoverySnapshotModules["librecovery_rlib"] = true
	ctx.Config().TestProductVariables.DirectedRecoverySnapshot = true

	// Check recovery snapshot output.
	snapshotDir := "recovery-snapshot"
	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")

	var includeJsonFiles []string

	for _, arch := range [][]string{
		[]string{"arm64", "armv8-a"},
	} {
		archType := arch[0]
		archVariant := arch[1]
		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)

		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
		rlibVariant := fmt.Sprintf("android_recovery_%s_%s_rlib_rlib-std", archType, archVariant)
		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")

		// Included modules
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "librecovery.so.json"))
		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_rlib", "librecovery_rlib.rlib", rlibDir, rlibVariant)
		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librecovery_rlib.rlib.json"))

		// TODO: When Rust supports the "prefer" property for prebuilts, perform this check.
		/*
			// Check that snapshot captures "prefer: true" prebuilt
			cc.CheckSnapshot(t, ctx, snapshotSingleton, "prebuilt_libfoo_rlib", "libfoo_rlib.rlib", rlibDir, rlibVariant)
			includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libfoo_rlib.rlib.json"))
		*/

		// Excluded modules. Modules not included in the directed recovery snapshot
		// are still included as fake modules.
		cc.CheckSnapshotRule(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "librecovery_available.so.json"))
		cc.CheckSnapshotRule(t, ctx, snapshotSingleton, "librecovery_available_rlib", "librecovery_available_rlib.rlib", rlibDir, rlibVariant)
		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librecovery_available_rlib.rlib.json"))
	}

	// Verify that each json file for an included module has a rule.
	for _, jsonFile := range includeJsonFiles {
		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
			t.Errorf("include json file %q not found, %#v", jsonFile, includeJsonFiles)
		}
	}
}
