Merge "sort release configs when creating artifacts" into main
diff --git a/android/makevars.go b/android/makevars.go
index f57ac45..f92f458 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -513,6 +513,7 @@
 		fmt.Fprintln(buf)
 	}
 	fmt.Fprintf(buf, ".KATI_READONLY := EXTRA_INSTALL_ZIPS\n")
+	fmt.Fprintf(buf, "$(KATI_visibility_prefix EXTRA_INSTALL_ZIPS,build/make/core/Makefile)\n")
 
 	for _, symlink := range symlinks {
 		fmt.Fprintf(buf, "%s:", symlink.to.String())
diff --git a/android/module_context.go b/android/module_context.go
index 3c1e30a..18adb30 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -497,6 +497,7 @@
 		partition:             fullInstallPath.partition,
 		skipInstall:           m.skipInstall(),
 		aconfigPaths:          m.getAconfigPaths(),
+		archType:              m.target.Arch.ArchType,
 	}
 	m.packagingSpecs = append(m.packagingSpecs, spec)
 	return spec
@@ -622,6 +623,7 @@
 		partition:        fullInstallPath.partition,
 		skipInstall:      m.skipInstall(),
 		aconfigPaths:     m.getAconfigPaths(),
+		archType:         m.target.Arch.ArchType,
 	})
 
 	return fullInstallPath
@@ -665,6 +667,7 @@
 		partition:        fullInstallPath.partition,
 		skipInstall:      m.skipInstall(),
 		aconfigPaths:     m.getAconfigPaths(),
+		archType:         m.target.Arch.ArchType,
 	})
 
 	return fullInstallPath
diff --git a/android/packaging.go b/android/packaging.go
index a7260a6..383f828 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -51,6 +51,9 @@
 
 	// Paths of aconfig files for the built artifact
 	aconfigPaths *Paths
+
+	// ArchType of the module which produced this packaging spec
+	archType ArchType
 }
 
 func (p *PackagingSpec) Equals(other *PackagingSpec) bool {
@@ -260,11 +263,31 @@
 
 func (p *PackagingBase) GatherPackagingSpecsWithFilter(ctx ModuleContext, filter func(PackagingSpec) bool) map[string]PackagingSpec {
 	m := make(map[string]PackagingSpec)
+
+	var arches []ArchType
+	for _, target := range p.getSupportedTargets(ctx) {
+		arches = append(arches, target.Arch.ArchType)
+	}
+
+	// filter out packaging specs for unsupported architecture
+	filterArch := func(ps PackagingSpec) bool {
+		for _, arch := range arches {
+			if arch == ps.archType {
+				return true
+			}
+		}
+		return false
+	}
+
 	ctx.VisitDirectDeps(func(child Module) {
 		if pi, ok := ctx.OtherModuleDependencyTag(child).(PackagingItem); !ok || !pi.IsPackagingItem() {
 			return
 		}
 		for _, ps := range child.TransitivePackagingSpecs() {
+			if !filterArch(ps) {
+				continue
+			}
+
 			if filter != nil {
 				if !filter(ps) {
 					continue
diff --git a/apex/apex.go b/apex/apex.go
index 9a80ec6..8029910 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1636,7 +1636,8 @@
 
 func apexFileForPrebuiltEtc(ctx android.BaseModuleContext, prebuilt prebuilt_etc.PrebuiltEtcModule, outputFile android.Path) apexFile {
 	dirInApex := filepath.Join(prebuilt.BaseDir(), prebuilt.SubDir())
-	return newApexFile(ctx, outputFile, outputFile.Base(), dirInApex, etc, prebuilt)
+	makeModuleName := strings.ReplaceAll(filepath.Join(dirInApex, outputFile.Base()), "/", "_")
+	return newApexFile(ctx, outputFile, makeModuleName, dirInApex, etc, prebuilt)
 }
 
 func apexFileForCompatConfig(ctx android.BaseModuleContext, config java.PlatformCompatConfigIntf, depName string) apexFile {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 9a5c2b4..74b2eec 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -11531,3 +11531,42 @@
 		"depend on java_aconfig_library not passed as an input",
 		aconfigFlagArgs, fmt.Sprintf("%s/%s/intermediate.pb", outDir, "quux"))
 }
+
+func TestMultiplePrebuiltsWithSameBase(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			prebuilts: ["myetc", "myetc2"],
+			min_sdk_version: "29",
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		prebuilt_etc {
+			name: "myetc",
+			src: "myprebuilt",
+			filename: "myfilename",
+		}
+		prebuilt_etc {
+			name: "myetc2",
+			sub_dir: "mysubdir",
+			src: "myprebuilt",
+			filename: "myfilename",
+		}
+	`, withFiles(android.MockFS{
+		"packages/modules/common/build/allowed_deps.txt": nil,
+	}))
+
+	ab := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, ctx, ab)
+	var builder strings.Builder
+	data.Custom(&builder, ab.BaseModuleName(), "TARGET_", "", data)
+	androidMk := builder.String()
+
+	android.AssertStringDoesContain(t, "not found", androidMk, "LOCAL_MODULE := etc_myfilename.myapex")
+	android.AssertStringDoesContain(t, "not found", androidMk, "LOCAL_MODULE := etc_mysubdir_myfilename.myapex")
+}
diff --git a/bin/aninja b/bin/aninja
new file mode 100755
index 0000000..cceb794
--- /dev/null
+++ b/bin/aninja
@@ -0,0 +1,25 @@
+#!/bin/bash -e
+
+# Copyright (C) 2022 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.
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+require_lunch
+
+cd $(gettop)
+prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-${TARGET_PRODUCT}.ninja "$@"
+
diff --git a/bin/overrideflags b/bin/overrideflags
new file mode 100755
index 0000000..e16537b
--- /dev/null
+++ b/bin/overrideflags
@@ -0,0 +1,100 @@
+#!/bin/bash -e
+# Copyright (C) 2023 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.
+
+
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+function print_help() {
+    echo -e "overrideflags is used to set default value for local build."
+    echo -e "\nOptions:"
+    echo -e "\t--release-config  \tPath to release configuration directory. Required"
+    echo -e "\t--no-edit         \tIf present, skip editing flag value file."
+    echo -e "\t-h/--help         \tShow this help."
+}
+
+function main() {
+    while (($# > 0)); do
+        case $1 in
+        --release-config)
+            if [[ $# -le 1 ]]; then
+                echo "--release-config requires a path"
+                return 1
+            fi
+            local release_config_dir="$2"
+            shift 2
+            ;;
+        --no-edit)
+            local no_edit="true"
+            shift 1
+            ;;
+        -h|--help)
+            print_help
+            return
+            ;;
+        *)
+            echo "$1 is unrecognized"
+            print_help
+            return 1
+            ;;
+        esac
+    done
+
+
+
+    case $(uname -s) in
+        Darwin)
+            local host_arch=darwin-x86
+            ;;
+        Linux)
+            local host_arch=linux-x86
+            ;;
+        *)
+            >&2 echo Unknown host $(uname -s)
+            return
+            ;;
+    esac
+
+    if [[ -z "${release_config_dir}" ]]; then
+        echo "Please provide release configuration path by --release-config"
+        exit 1
+    elif [ ! -d "${release_config_dir}" ]; then
+        echo "${release_config_dir} is an invalid directory"
+        exit 1
+    fi
+    local T="$(gettop)"
+    local aconfig_dir="${T}"/build/make/tools/aconfig/
+    local overrideflag_py="${aconfig_dir}"/overrideflags/overrideflags.py
+    local overridefile="${release_config_dir}/aconfig/override_values.textproto"
+
+    # Edit override file
+    if [[ -z "${no_edit}" ]]; then
+        editor="${EDITOR:-$(which vim)}"
+
+        eval "${editor} ${overridefile}"
+        if [ $? -ne 0 ]; then
+            echo "Fail to set override values"
+            return 1
+        fi
+    fi
+
+    ${T}/prebuilts/build-tools/${host_arch}/bin/py3-cmd -u "${overrideflag_py}" \
+        --overrides "${overridefile}" \
+        --out "${release_config_dir}/aconfig"
+}
+
+
+main "$@"
diff --git a/cc/cmake_ext_add_aidl_library.txt b/cc/cmake_ext_add_aidl_library.txt
index dcf805a..af5bdf6 100644
--- a/cc/cmake_ext_add_aidl_library.txt
+++ b/cc/cmake_ext_add_aidl_library.txt
@@ -1,35 +1,51 @@
-function(add_aidl_library NAME LANG SOURCES AIDLFLAGS)
+function(add_aidl_library NAME LANG AIDLROOT SOURCES AIDLFLAGS)
     if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.20")
         cmake_policy(SET CMP0116 NEW)
     endif()
 
+    # Strip trailing slash
+    get_filename_component(AIDLROOT_TRAILING "${AIDLROOT}" NAME)
+    if ("${AIDLROOT_TRAILING}" STREQUAL "")
+        get_filename_component(AIDLROOT "${AIDLROOT}foo" DIRECTORY)
+    endif()
+
     set(GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/.intermediates/${NAME}-source")
     set(GEN_SOURCES)
-    foreach(SOURCE ${SOURCES})
-        get_filename_component(SOURCE_WE ${SOURCE} NAME_WE)
-        get_filename_component(SOURCE_ABSOLUTE ${SOURCE} ABSOLUTE)
-        get_filename_component(SOURCE_DIR ${SOURCE_ABSOLUTE} DIRECTORY)
-        set(GEN_SOURCE "${GEN_DIR}/${SOURCE_WE}.cpp")
+    foreach (SOURCE ${SOURCES})
+        set(SOURCE_FULL ${AIDLROOT}/${SOURCE})
+        get_filename_component(SOURCE_WLE ${SOURCE} NAME_WLE)
+        get_filename_component(SOURCE_SUBDIR ${SOURCE} DIRECTORY)
+        set(GEN_SOURCE "${GEN_DIR}/${SOURCE_SUBDIR}/${SOURCE_WLE}.cpp")
+
+        file(READ "${SOURCE}" SOURCE_CONTENTS)
+        string(FIND "${SOURCE_CONTENTS}" "@VintfStability" VINTF_MATCH)
+        set(STABILITY_FLAG)
+        if (${VINTF_MATCH} GREATER_EQUAL 0)
+            set(STABILITY_FLAG --stability vintf)
+        endif()
+
         set(DEPFILE_ARG)
         if (NOT ${CMAKE_GENERATOR} MATCHES "Unix Makefiles")
             set(DEPFILE_ARG DEPFILE "${GEN_SOURCE}.d")
         endif()
+
         add_custom_command(
             OUTPUT "${GEN_SOURCE}"
-            MAIN_DEPENDENCY "${SOURCE_ABSOLUTE}"
+            MAIN_DEPENDENCY "${SOURCE_FULL}"
             ${DEPFILE_ARG}
             COMMAND "${AIDL_BIN}"
             ARGS
             --lang=${LANG}
-            --include="${SOURCE_DIR}"
+            --include="${AIDLROOT}"
             --dep="${GEN_SOURCE}.d"
             --out="${GEN_DIR}"
             --header_out="${GEN_DIR}/include"
             --ninja
             --structured
             --min_sdk_version=current
+            ${STABILITY_FLAG}
             ${AIDLFLAGS}
-            "${SOURCE_ABSOLUTE}"
+            "${SOURCE_FULL}"
         )
         list(APPEND GEN_SOURCES "${GEN_SOURCE}")
     endforeach()
@@ -39,9 +55,14 @@
     target_include_directories(${NAME}
         PUBLIC
         "${GEN_DIR}/include"
-        "${ANDROID_BUILD_TOP}/frameworks/native/libs/binder/ndk/include_${LANG}"
     )
+
+    if (${LANG} MATCHES "ndk")
+        set(BINDER_LIB_NAME "libbinder_ndk_sdk")
+    else()
+        set(BINDER_LIB_NAME "libbinder_sdk")
+    endif()
     target_link_libraries(${NAME}
-        libbinder_sdk
+        ${BINDER_LIB_NAME}
     )
 endfunction()
diff --git a/cc/cmake_module_aidl.txt b/cc/cmake_module_aidl.txt
index 4509a88..84755a3 100644
--- a/cc/cmake_module_aidl.txt
+++ b/cc/cmake_module_aidl.txt
@@ -1,8 +1,11 @@
 # <<.M.Name>>
 
-<<setList .M.Name "_SRCS" "${ANDROID_BUILD_TOP}/" (getCompilerProperties .M).AidlInterface.Sources>>
+<<setList .M.Name "_SRCS" "" (getAidlSources .M)>>
 
 <<setList .M.Name "_AIDLFLAGS" "" (getCompilerProperties .M).AidlInterface.Flags>>
 
-add_aidl_library(<<.M.Name>> <<(getCompilerProperties .M).AidlInterface.Lang>> "${<<.M.Name>>_SRCS}" "${<<.M.Name>>_AIDLFLAGS}")
+add_aidl_library(<<.M.Name>> <<(getCompilerProperties .M).AidlInterface.Lang>>
+    "${ANDROID_BUILD_TOP}/<<.Ctx.OtherModuleDir .M>>/<<(getCompilerProperties .M).AidlInterface.AidlRoot>>"
+    "${<<.M.Name>>_SRCS}"
+    "${<<.M.Name>>_AIDLFLAGS}")
 add_library(android::<<.M.Name>> ALIAS <<.M.Name>>)
diff --git a/cc/cmake_module_cc.txt b/cc/cmake_module_cc.txt
index 571f27c..488e5e1 100644
--- a/cc/cmake_module_cc.txt
+++ b/cc/cmake_module_cc.txt
@@ -1,7 +1,7 @@
 <<$srcs := getSources .M>>
 <<$includeDirs := getIncludeDirs .Ctx .M>>
 <<$cflags := (getCompilerProperties .M).Cflags>>
-<<$deps := mapLibraries (concat5
+<<$deps := mapLibraries .Ctx .M (concat5
 (getLinkerProperties .M).Whole_static_libs
 (getLinkerProperties .M).Static_libs
 (getLinkerProperties .M).Shared_libs
diff --git a/cc/cmake_snapshot.go b/cc/cmake_snapshot.go
index 0635a29..c21a46f 100644
--- a/cc/cmake_snapshot.go
+++ b/cc/cmake_snapshot.go
@@ -192,13 +192,16 @@
 		},
 		"getExtraLibs":   getExtraLibs,
 		"getIncludeDirs": getIncludeDirs,
-		"mapLibraries": func(libs []string, mapping map[string]LibraryMappingProperty) []string {
+		"mapLibraries": func(ctx android.ModuleContext, m *Module, libs []string, mapping map[string]LibraryMappingProperty) []string {
 			var mappedLibs []string
 			for _, lib := range libs {
 				mappedLib, exists := mapping[lib]
 				if exists {
 					lib = mappedLib.Mapped_name
 				} else {
+					if !ctx.OtherModuleExists(lib) {
+						ctx.OtherModuleErrorf(m, "Dependency %s doesn't exist", lib)
+					}
 					lib = "android::" + lib
 				}
 				if lib == "" {
@@ -210,6 +213,21 @@
 			mappedLibs = slices.Compact(mappedLibs)
 			return mappedLibs
 		},
+		"getAidlSources": func(m *Module) []string {
+			aidlInterface := m.compiler.baseCompilerProps().AidlInterface
+			aidlRoot := aidlInterface.AidlRoot + string(filepath.Separator)
+			if aidlInterface.AidlRoot == "" {
+				aidlRoot = ""
+			}
+			var sources []string
+			for _, src := range aidlInterface.Sources {
+				if !strings.HasPrefix(src, aidlRoot) {
+					panic(fmt.Sprintf("Aidl source '%v' doesn't start with '%v'", src, aidlRoot))
+				}
+				sources = append(sources, src[len(aidlRoot):])
+			}
+			return sources
+		},
 	}
 
 	return template.Must(template.New("").Delims("<<", ">>").Funcs(funcMap).Parse(templateContents))
@@ -282,14 +300,14 @@
 	var pregeneratedModules []*Module
 	ctx.WalkDeps(func(dep_a android.Module, parent android.Module) bool {
 		moduleName := ctx.OtherModuleName(dep_a)
-		dep, ok := dep_a.(*Module)
-		if !ok {
-			return false // not a cc module
-		}
 		if visited := visitedModules[moduleName]; visited {
 			return false // visit only once
 		}
 		visitedModules[moduleName] = true
+		dep, ok := dep_a.(*Module)
+		if !ok {
+			return false // not a cc module
+		}
 		if mapping, ok := pprop.LibraryMapping[moduleName]; ok {
 			if mapping.Package_pregenerated != "" {
 				pregeneratedModules = append(pregeneratedModules, dep)
diff --git a/cc/compiler.go b/cc/compiler.go
index aee584d..9187b9b 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -147,6 +147,9 @@
 		// list of aidl_interface sources
 		Sources []string `blueprint:"mutated"`
 
+		// root directory of AIDL sources
+		AidlRoot string `blueprint:"mutated"`
+
 		// AIDL backend language (e.g. "cpp", "ndk")
 		Lang string `blueprint:"mutated"`
 
diff --git a/filesystem/aconfig_files.go b/filesystem/aconfig_files.go
index 44de202..8daee85 100644
--- a/filesystem/aconfig_files.go
+++ b/filesystem/aconfig_files.go
@@ -22,7 +22,7 @@
 	"github.com/google/blueprint/proptools"
 )
 
-func (f *filesystem) buildAconfigFlagsFiles(ctx android.ModuleContext, builder *android.RuleBuilder, specs map[string]android.PackagingSpec, dir android.Path) {
+func (f *filesystem) buildAconfigFlagsFiles(ctx android.ModuleContext, builder *android.RuleBuilder, specs map[string]android.PackagingSpec, dir android.OutputPath) {
 	if !proptools.Bool(f.properties.Gen_aconfig_flags_pb) {
 		return
 	}
@@ -31,15 +31,6 @@
 	aconfigToolPath := ctx.Config().HostToolPath(ctx, "aconfig")
 	cmd := builder.Command().Tool(aconfigFlagsBuilderPath).Implicit(aconfigToolPath)
 
-	installAconfigFlags := filepath.Join(dir.String(), "etc", "aconfig_flags_"+f.partitionName()+".pb")
-
-	var sb strings.Builder
-	sb.WriteString("set -e\n")
-	sb.WriteString(aconfigToolPath.String())
-	sb.WriteString(" dump-cache --dedup --format protobuf --out ")
-	sb.WriteString(installAconfigFlags)
-	sb.WriteString(" \\\n")
-
 	var caches []string
 	for _, ps := range specs {
 		cmd.Implicits(ps.GetAconfigPaths())
@@ -47,12 +38,45 @@
 	}
 	caches = android.SortedUniqueStrings(caches)
 
+	var sbCaches strings.Builder
 	for _, cache := range caches {
-		sb.WriteString("  --cache ")
-		sb.WriteString(cache)
-		sb.WriteString(" \\\n")
+		sbCaches.WriteString("  --cache ")
+		sbCaches.WriteString(cache)
+		sbCaches.WriteString(" \\\n")
 	}
+	sbCaches.WriteRune('\n')
+
+	var sb strings.Builder
+	sb.WriteString("set -e\n")
+
+	installAconfigFlagsPath := dir.Join(ctx, "etc", "aconfig_flags.pb")
+	sb.WriteString(aconfigToolPath.String())
+	sb.WriteString(" dump-cache --dedup --format protobuf --out ")
+	sb.WriteString(installAconfigFlagsPath.String())
+	sb.WriteString(" \\\n")
+	sb.WriteString(sbCaches.String())
+	cmd.ImplicitOutput(installAconfigFlagsPath)
+
+	installAconfigStorageDir := dir.Join(ctx, "etc", "aconfig")
+	sb.WriteString("mkdir -p ")
+	sb.WriteString(installAconfigStorageDir.String())
 	sb.WriteRune('\n')
 
+	generatePartitionAconfigStorageFile := func(fileType, fileName string) {
+		sb.WriteString(aconfigToolPath.String())
+		sb.WriteString(" create-storage --container ")
+		sb.WriteString(f.PartitionType())
+		sb.WriteString(" --file ")
+		sb.WriteString(fileType)
+		sb.WriteString(" --out ")
+		sb.WriteString(filepath.Join(installAconfigStorageDir.String(), fileName))
+		sb.WriteString(" \\\n")
+		sb.WriteString(sbCaches.String())
+		cmd.ImplicitOutput(installAconfigStorageDir.Join(ctx, fileName))
+	}
+	generatePartitionAconfigStorageFile("package_map", "package.map")
+	generatePartitionAconfigStorageFile("flag_map", "flag.map")
+	generatePartitionAconfigStorageFile("flag_val", "flag.val")
+
 	android.WriteExecutableFileRuleVerbatim(ctx, aconfigFlagsBuilderPath, sb.String())
 }
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index 861918f..015d39a 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -497,3 +497,70 @@
 		android.AssertStringListContains(t, "missing entry", fs.entries, e)
 	}
 }
+
+func TestFilterOutUnsupportedArches(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+		android_filesystem {
+			name: "fs_64_only",
+			deps: ["foo"],
+		}
+
+		android_filesystem {
+			name: "fs_64_32",
+			compile_multilib: "both",
+			multilib: {
+				first: {
+					deps: ["foo"],
+				},
+			},
+		}
+
+		cc_binary {
+			name: "foo",
+			required: ["phony"],
+		}
+
+		phony {
+			name: "phony",
+			required: [
+				"libbar",
+				"app",
+			],
+		}
+
+		cc_library {
+			name: "libbar",
+		}
+
+		android_app {
+			name: "app",
+			srcs: ["a.java"],
+			platform_apis: true,
+		}
+	`)
+	testcases := []struct {
+		fsName     string
+		expected   []string
+		unexpected []string
+	}{
+		{
+			fsName:     "fs_64_only",
+			expected:   []string{"app/app/app.apk", "bin/foo", "lib64/libbar.so"},
+			unexpected: []string{"lib/libbar.so"},
+		},
+		{
+			fsName:     "fs_64_32",
+			expected:   []string{"app/app/app.apk", "bin/foo", "lib64/libbar.so", "lib/libbar.so"},
+			unexpected: []string{},
+		},
+	}
+	for _, c := range testcases {
+		fs := result.ModuleForTests(c.fsName, "android_common").Module().(*filesystem)
+		for _, e := range c.expected {
+			android.AssertStringListContains(t, "missing entry", fs.entries, e)
+		}
+		for _, e := range c.unexpected {
+			android.AssertStringListDoesNotContain(t, "unexpected entry", fs.entries, e)
+		}
+	}
+}