aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYuexi Ma <yuexima@google.com>2021-08-18 20:21:40 -0700
committerYuexi Ma <yuexima@google.com>2021-08-23 23:04:19 -0700
commitb2f9e540a171a389d4cfe4ca7c3a49d7b798234b (patch)
treeb2d4068f1e62428eae11be5091cee7c810e0a2d6
parent63aebf250bc079f991a6b00b995287bb6649c78e (diff)
downloadplatform_test_app_compat_csuite-b2f9e540a171a389d4cfe4ca7c3a49d7b798234b.tar.gz
platform_test_app_compat_csuite-b2f9e540a171a389d4cfe4ca7c3a49d7b798234b.tar.bz2
platform_test_app_compat_csuite-b2f9e540a171a389d4cfe4ca7c3a49d7b798234b.zip
Replace the package name providers with module info providers
Create a new interface for the module generator to allow more customization in the providers. Test: atest csuite-harness-tests Test: make csuite; csuite-tradefed run csuite-app-launch --enable-module-dynamic-download --dynamic-download-args <args> --package <package> Change-Id: I2950129b7092bd0d07b9b1eb67d2bb37e609ead3
-rw-r--r--harness/src/main/java/com/android/csuite/config/ModuleGenerator.java257
-rw-r--r--harness/src/main/java/com/android/csuite/core/CommandLinePackageNameProvider.java39
-rw-r--r--harness/src/main/java/com/android/csuite/core/FileBasedPackageNameProvider.java60
-rw-r--r--harness/src/main/java/com/android/csuite/core/ModuleGenerator.java220
-rw-r--r--harness/src/main/java/com/android/csuite/core/ModuleInfoProvider.java (renamed from harness/src/main/java/com/android/csuite/core/PackageNameProvider.java)36
-rw-r--r--harness/src/main/java/com/android/csuite/core/ModuleTemplate.java58
-rw-r--r--harness/src/main/java/com/android/csuite/core/PackageModuleInfoProvider.java70
-rw-r--r--harness/src/main/java/com/android/csuite/core/PackagesFileModuleInfoProvider.java94
-rw-r--r--harness/src/main/resources/config/csuite-base.xml6
-rw-r--r--harness/src/test/java/com/android/csuite/CSuiteUnitTests.java7
-rw-r--r--harness/src/test/java/com/android/csuite/config/ModuleGeneratorTest.java321
-rw-r--r--harness/src/test/java/com/android/csuite/core/CommandLinePackageNameProviderTest.java45
-rw-r--r--harness/src/test/java/com/android/csuite/core/FileBasedPackageNameProviderTest.java116
-rw-r--r--harness/src/test/java/com/android/csuite/core/ModuleGeneratorTest.java315
-rw-r--r--harness/src/test/java/com/android/csuite/core/ModuleTemplateTest.java82
-rw-r--r--harness/src/test/java/com/android/csuite/core/PackageModuleInfoProviderTest.java142
-rw-r--r--harness/src/test/java/com/android/csuite/core/PackagesFileModuleInfoProviderTest.java200
-rw-r--r--tools/csuite_test/csuite_test.go10
18 files changed, 1219 insertions, 859 deletions
diff --git a/harness/src/main/java/com/android/csuite/config/ModuleGenerator.java b/harness/src/main/java/com/android/csuite/config/ModuleGenerator.java
deleted file mode 100644
index 0760085..0000000
--- a/harness/src/main/java/com/android/csuite/config/ModuleGenerator.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (C) 2020 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 com.android.csuite.config;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.csuite.core.PackageNameProvider;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.IConfiguration;
-import com.android.tradefed.config.IConfigurationReceiver;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.config.Option.Importance;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.invoker.TestInformation;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.targetprep.ITargetPreparer;
-import com.android.tradefed.testtype.IBuildReceiver;
-import com.android.tradefed.testtype.IRemoteTest;
-import com.android.tradefed.testtype.IShardableTest;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.io.Resources;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * A tool for generating TradeFed suite modules during runtime.
- *
- * <p>This class generates module config files into TradeFed's test directory at runtime using a
- * template. Since the content of the test directory relies on what is being generated in a test
- * run, there can only be one instance executing at a given time.
- *
- * <p>The intention of this class is to generate test modules at the beginning of a test run and
- * cleans up after all tests finish, which resembles a target preparer. However, a target preparer
- * is executed after the sharding process has finished. The only way to make the generated modules
- * available for sharding without making changes to TradeFed's core code is to disguise this module
- * generator as an instance of IShardableTest and declare it separately in test plan config. This is
- * hacky, and in the long term a TradeFed centered solution is desired. For more details, see
- * go/sharding-hack-for-module-gen. Note that since the generate step is executed as a test instance
- * and cleanup step is executed as a target preparer, there should be no saved states between
- * generating and cleaning up module files.
- *
- * <p>This module generator collects package names from all PackageNameProvider objects specified in
- * the test configs.
- *
- * <h2>Syntax and usage</h2>
- *
- * <p>References to package name providers in TradeFed test configs must have the following syntax:
- *
- * <blockquote>
- *
- * <b>&lt;object type="PACKAGE_NAME_PROVIDER" class="</b><i>provider_class_name</i><b>"/&gt;</b>
- *
- * </blockquote>
- *
- * where <i>provider_class_name</i> is the fully-qualified class name of an PackageNameProvider
- * implementation class.
- */
-public final class ModuleGenerator
- implements IRemoteTest,
- IShardableTest,
- IBuildReceiver,
- ITargetPreparer,
- IConfigurationReceiver {
-
- @VisibleForTesting static final String MODULE_FILE_EXTENSION = ".config";
- @VisibleForTesting static final String OPTION_TEMPLATE = "template";
- @VisibleForTesting static final String PACKAGE_NAME_PROVIDER = "PACKAGE_NAME_PROVIDER";
- private static final String TEMPLATE_PACKAGE_PATTERN = "\\{package\\}";
- private static final Collection<IRemoteTest> NOT_SPLITABLE = null;
-
- @Option(
- name = OPTION_TEMPLATE,
- description = "Module config template resource path.",
- importance = Importance.ALWAYS)
- private String mTemplate;
-
- private final TestDirectoryProvider mTestDirectoryProvider;
- private final ResourceLoader mResourceLoader;
- private final FileSystem mFileSystem;
- private IBuildInfo mBuildInfo;
- private IConfiguration mConfiguration;
-
- @Override
- public void setConfiguration(IConfiguration configuration) {
- mConfiguration = configuration;
- }
-
- public ModuleGenerator() {
- this(FileSystems.getDefault());
- }
-
- private ModuleGenerator(FileSystem fileSystem) {
- this(
- fileSystem,
- new CompatibilityTestDirectoryProvider(fileSystem),
- new ClassResourceLoader());
- }
-
- @VisibleForTesting
- ModuleGenerator(
- FileSystem fileSystem,
- TestDirectoryProvider testDirectoryProvider,
- ResourceLoader resourceLoader) {
- mFileSystem = fileSystem;
- mTestDirectoryProvider = testDirectoryProvider;
- mResourceLoader = resourceLoader;
- }
-
- @Override
- public void run(final TestInformation testInfo, final ITestInvocationListener listener) {
- // Intentionally left blank since this class is not really a test.
- }
-
- @Override
- public void setUp(TestInformation testInfo) {
- // Intentionally left blank.
- }
-
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mBuildInfo = buildInfo;
- }
-
- /**
- * Generates test modules. Note that the implementation of this method is not related to
- * sharding in any way.
- */
- @Override
- public Collection<IRemoteTest> split() {
- try {
- // Executes the generate step.
- generateModules();
- } catch (IOException e) {
- throw new UncheckedIOException("Failed to generate modules", e);
- }
-
- return NOT_SPLITABLE;
- }
-
- /** Cleans up generated test modules. */
- @Override
- public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
- // Gets build info from test info as when the class is executed as a ITargetPreparer
- // preparer, it is not considered as a IBuildReceiver instance.
- mBuildInfo = testInfo.getBuildInfo();
-
- try {
- // Executes the clean up step.
- cleanUpModules();
- } catch (IOException ioException) {
- throw new UncheckedIOException("Failed to clean up generated modules", ioException);
- }
- }
-
- private Set<String> getPackageNames() throws IOException {
- Set<String> packages = new HashSet<>();
- for (Object provider : mConfiguration.getConfigurationObjectList(PACKAGE_NAME_PROVIDER)) {
- packages.addAll(((PackageNameProvider) provider).get());
- }
- return packages;
- }
-
- private void generateModules() throws IOException {
- String templateContent = mResourceLoader.load(mTemplate);
-
- for (String packageName : getPackageNames()) {
- validatePackageName(packageName);
- Files.write(
- getModulePath(packageName),
- templateContent.replaceAll(TEMPLATE_PACKAGE_PATTERN, packageName).getBytes());
- }
- }
-
- private void cleanUpModules() throws IOException {
- getPackageNames()
- .forEach(
- packageName -> {
- try {
- Files.delete(getModulePath(packageName));
- } catch (IOException ioException) {
- CLog.e(
- "Failed to delete the generated module for package "
- + packageName,
- ioException);
- }
- });
- }
-
- private Path getModulePath(String packageName) throws IOException {
- Path testsDir = mTestDirectoryProvider.get(mBuildInfo);
- return testsDir.resolve(packageName + MODULE_FILE_EXTENSION);
- }
-
- private static void validatePackageName(String packageName) {
- if (packageName.isEmpty() || packageName.matches(".*" + TEMPLATE_PACKAGE_PATTERN + ".*")) {
- throw new IllegalArgumentException(
- "Package name cannot be empty or contains package placeholder: "
- + TEMPLATE_PACKAGE_PATTERN);
- }
- }
-
- @VisibleForTesting
- interface ResourceLoader {
- String load(String resourceName) throws IOException;
- }
-
- private static final class ClassResourceLoader implements ResourceLoader {
- @Override
- public String load(String resourceName) throws IOException {
- return Resources.toString(
- getClass().getClassLoader().getResource(resourceName), StandardCharsets.UTF_8);
- }
- }
-
- @VisibleForTesting
- interface TestDirectoryProvider {
- Path get(IBuildInfo buildInfo) throws IOException;
- }
-
- private static final class CompatibilityTestDirectoryProvider implements TestDirectoryProvider {
- private final FileSystem mFileSystem;
-
- private CompatibilityTestDirectoryProvider(FileSystem fileSystem) {
- mFileSystem = fileSystem;
- }
-
- @Override
- public Path get(IBuildInfo buildInfo) throws IOException {
- return mFileSystem.getPath(
- new CompatibilityBuildHelper(buildInfo).getTestsDir().getPath());
- }
- }
-}
diff --git a/harness/src/main/java/com/android/csuite/core/CommandLinePackageNameProvider.java b/harness/src/main/java/com/android/csuite/core/CommandLinePackageNameProvider.java
deleted file mode 100644
index 33426ed..0000000
--- a/harness/src/main/java/com/android/csuite/core/CommandLinePackageNameProvider.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 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 com.android.csuite.core;
-
-import com.android.tradefed.config.Option;
-import com.android.tradefed.config.OptionClass;
-
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/** A package name provider that accepts package names via a command line option. */
-@OptionClass(alias = "command-line-package-name-provider")
-public final class CommandLinePackageNameProvider implements PackageNameProvider {
- @VisibleForTesting static final String PACKAGE = "package";
-
- @Option(name = PACKAGE, description = "App package names.")
- private final Set<String> mPackages = new HashSet<>();
-
- @Override
- public Set<String> get() {
- return mPackages;
- }
-}
diff --git a/harness/src/main/java/com/android/csuite/core/FileBasedPackageNameProvider.java b/harness/src/main/java/com/android/csuite/core/FileBasedPackageNameProvider.java
deleted file mode 100644
index 6f6af06..0000000
--- a/harness/src/main/java/com/android/csuite/core/FileBasedPackageNameProvider.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 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 com.android.csuite.core;
-
-import com.android.tradefed.config.Option;
-
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/** A package name provider that accepts files that contains package names. */
-public final class FileBasedPackageNameProvider implements PackageNameProvider {
- @VisibleForTesting static final String PACKAGES_FILE = "packages-file";
- @VisibleForTesting static final String COMMENT_LINE_PREFIX = "#";
-
- @Option(
- name = PACKAGES_FILE,
- description =
- "File paths that contain package names separated by newline characters."
- + " Comment lines are supported only if the lines start with double slash."
- + " Trailing comments are not supported. Empty lines are ignored.")
- private final Set<File> mPackagesFiles = new HashSet<>();
-
- @Override
- public Set<String> get() throws IOException {
- Set<String> packages = new HashSet<>();
- for (File packagesFile : mPackagesFiles) {
- packages.addAll(
- Files.readAllLines(packagesFile.toPath()).parallelStream()
- .map(String::trim)
- .filter(this::isPackageName)
- .collect(Collectors.toSet()));
- }
- return packages;
- }
-
- private boolean isPackageName(String text) {
- // Check the text is not an empty string and not a comment line.
- return !text.isEmpty() && !text.startsWith(COMMENT_LINE_PREFIX);
- }
-}
diff --git a/harness/src/main/java/com/android/csuite/core/ModuleGenerator.java b/harness/src/main/java/com/android/csuite/core/ModuleGenerator.java
new file mode 100644
index 0000000..2bc62cf
--- /dev/null
+++ b/harness/src/main/java/com/android/csuite/core/ModuleGenerator.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2020 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 com.android.csuite.core;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.IShardableTest;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.errorprone.annotations.MustBeClosed;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * Generates TradeFed suite modules during runtime.
+ *
+ * <p>This class generates module config files into TradeFed's test directory at runtime using a
+ * template. Since the content of the test directory relies on what is being generated in a test
+ * run, there can only be one instance executing at a given time.
+ *
+ * <p>The intention of this class is to generate test modules at the beginning of a test run and
+ * cleans up after all tests finish, which resembles a target preparer. However, a target preparer
+ * is executed after the sharding process has finished. The only way to make the generated modules
+ * available for sharding without making changes to TradeFed's core code is to disguise this module
+ * generator as an instance of IShardableTest and declare it separately in test plan config. This is
+ * hacky, and in the long term a TradeFed centered solution is desired. For more details, see
+ * go/sharding-hack-for-module-gen. Note that since the generate step is executed as a test instance
+ * and cleanup step is executed as a target preparer, there should be no saved states between
+ * generating and cleaning up module files.
+ *
+ * <p>This module generator collects modules' info from all ModuleInfoProvider objects specified in
+ * the test plan config.
+ *
+ * <h2>Syntax and usage</h2>
+ *
+ * <p>References to module info providers in TradeFed test plan config must have the following
+ * syntax:
+ *
+ * <blockquote>
+ *
+ * <b>&lt;object type="MODULE_INFO_PROVIDER" class="</b><i>provider_class_name</i><b>"/&gt;</b>
+ *
+ * </blockquote>
+ *
+ * where <i>provider_class_name</i> is the fully-qualified class name of an ModuleInfoProvider
+ * implementation class.
+ */
+public final class ModuleGenerator
+ implements IRemoteTest,
+ IShardableTest,
+ IBuildReceiver,
+ IConfigurationReceiver,
+ ITargetPreparer {
+ @VisibleForTesting static final String MODULE_FILE_NAME_EXTENSION = ".config";
+ @VisibleForTesting static final String MODULE_INFO_PROVIDER = "MODULE_INFO_PROVIDER";
+ private static final Collection<IRemoteTest> NOT_SPLITTABLE = null;
+
+ private final TestDirectoryProvider mTestDirectoryProvider;
+ private IBuildInfo mBuildInfo;
+ private IConfiguration mConfiguration;
+
+ public ModuleGenerator() {
+ this(buildInfo -> new CompatibilityBuildHelper(buildInfo).getTestsDir().toPath());
+ }
+
+ @VisibleForTesting
+ ModuleGenerator(TestDirectoryProvider testDirectoryProvider) {
+ mTestDirectoryProvider = testDirectoryProvider;
+ }
+
+ @Override
+ public void setUp(TestInformation testInfo) {
+ // Do not add cleanup code here as this method is executed after the split method.
+ }
+
+ /**
+ * Cleans up the generated test modules files.
+ *
+ * <p>Note that this method does not execute from the same instance of this class that generates
+ * the modules so be careful when using any class fields.
+ */
+ @Override
+ public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ // Gets build info from test info as when the class is executed as a ITargetPreparer
+ // preparer, it is not considered as a IBuildReceiver instance.
+ mBuildInfo = testInfo.getBuildInfo();
+
+ try {
+ deleteModuleFiles();
+ } catch (IOException ioException) {
+ throw new UncheckedIOException(ioException);
+ }
+ }
+
+ private void deleteModuleFiles() throws IOException {
+ Files.list(mTestDirectoryProvider.get(mBuildInfo))
+ .filter(Files::isRegularFile)
+ .filter(path -> path.toString().endsWith(MODULE_FILE_NAME_EXTENSION))
+ .forEach(
+ path -> {
+ try {
+ Files.delete(path);
+ } catch (IOException ioException) {
+ throw new UncheckedIOException(ioException);
+ }
+ });
+ }
+
+ private void generateModules() throws IOException {
+ deleteModuleFiles();
+ try (Stream<ModuleInfoProvider.ModuleInfo> modulesInfo = getModulesInfo()) {
+ Set<String> moduleNames = new HashSet<>();
+ modulesInfo.forEachOrdered(
+ moduleInfo -> {
+ String moduleName = moduleInfo.getName().trim();
+ if (moduleName.isEmpty()) {
+ throw new IllegalArgumentException("Module name cannot be empty.");
+ }
+
+ if (moduleNames.contains(moduleName)) {
+ throw new IllegalArgumentException(
+ "Duplicated module name: " + moduleName);
+ }
+
+ try {
+ Files.write(
+ getModulePath(moduleName), moduleInfo.getContent().getBytes());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ moduleNames.add(moduleName);
+ });
+ }
+ }
+
+ @MustBeClosed
+ @SuppressWarnings("MustBeClosedChecker")
+ private Stream<ModuleInfoProvider.ModuleInfo> getModulesInfo() {
+ return mConfiguration.getConfigurationObjectList(MODULE_INFO_PROVIDER).stream()
+ .map(obj -> (ModuleInfoProvider) obj)
+ .flatMap(
+ info -> {
+ try {
+ return info.get();
+ } catch (IOException ioException) {
+ throw new UncheckedIOException(ioException);
+ }
+ });
+ }
+
+ /**
+ * Generates test modules. Note that the implementation of this method is not related to
+ * sharding in any way.
+ */
+ @Override
+ public Collection<IRemoteTest> split() {
+ try {
+ generateModules();
+ } catch (IOException ioException) {
+ throw new UncheckedIOException(ioException);
+ }
+ return NOT_SPLITTABLE;
+ }
+
+ private Path getModulePath(String moduleName) throws IOException {
+ return mTestDirectoryProvider
+ .get(mBuildInfo)
+ .resolve(moduleName + MODULE_FILE_NAME_EXTENSION);
+ }
+
+ @Override
+ public void run(final TestInformation testInfo, final ITestInvocationListener listener) {
+ // Intentionally left blank since this class is not really a test.
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mBuildInfo = buildInfo;
+ }
+
+ @Override
+ public void setConfiguration(IConfiguration configuration) {
+ mConfiguration = configuration;
+ }
+
+ @VisibleForTesting
+ interface TestDirectoryProvider {
+ Path get(IBuildInfo buildInfo) throws IOException;
+ }
+}
diff --git a/harness/src/main/java/com/android/csuite/core/PackageNameProvider.java b/harness/src/main/java/com/android/csuite/core/ModuleInfoProvider.java
index 05709a1..7a2c6af 100644
--- a/harness/src/main/java/com/android/csuite/core/PackageNameProvider.java
+++ b/harness/src/main/java/com/android/csuite/core/ModuleInfoProvider.java
@@ -16,16 +16,30 @@
package com.android.csuite.core;
+import com.google.errorprone.annotations.MustBeClosed;
+
import java.io.IOException;
-import java.util.Set;
-
-/** Provides a list of package names. */
-public interface PackageNameProvider {
- /**
- * Returns a set of package names.
- *
- * @return the package names. An empty set is returned if no package names are to be provided.
- * @throws IOException if failed to get package names.
- */
- Set<String> get() throws IOException;
+import java.util.stream.Stream;
+
+public interface ModuleInfoProvider {
+ final class ModuleInfo {
+ private final String mName;
+ private final String mContent;
+
+ public ModuleInfo(String name, String content) {
+ mName = name;
+ mContent = content;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getContent() {
+ return mContent;
+ }
+ }
+
+ @MustBeClosed
+ Stream<ModuleInfo> get() throws IOException;
}
diff --git a/harness/src/main/java/com/android/csuite/core/ModuleTemplate.java b/harness/src/main/java/com/android/csuite/core/ModuleTemplate.java
new file mode 100644
index 0000000..36331e9
--- /dev/null
+++ b/harness/src/main/java/com/android/csuite/core/ModuleTemplate.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 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 com.android.csuite.core;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.io.Resources;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+public final class ModuleTemplate {
+ private final String mTemplateContent;
+
+ public static ModuleTemplate load(String template, ResourceLoader resourceLoader)
+ throws IOException {
+ return new ModuleTemplate(resourceLoader.load(template));
+ }
+
+ @VisibleForTesting
+ ModuleTemplate(String templateContent) {
+ mTemplateContent = templateContent;
+ }
+
+ public String substitute(Map<String, String> replacementPairs) {
+ return replacementPairs.keySet().stream()
+ .reduce(
+ mTemplateContent,
+ (res, placeholder) ->
+ res.replace(placeholder, replacementPairs.get(placeholder)));
+ }
+
+ public interface ResourceLoader {
+ String load(String resourceName) throws IOException;
+ }
+
+ public static final class ClassResourceLoader implements ResourceLoader {
+ @Override
+ public String load(String resourceName) throws IOException {
+ return Resources.toString(
+ getClass().getClassLoader().getResource(resourceName), StandardCharsets.UTF_8);
+ }
+ }
+}
diff --git a/harness/src/main/java/com/android/csuite/core/PackageModuleInfoProvider.java b/harness/src/main/java/com/android/csuite/core/PackageModuleInfoProvider.java
new file mode 100644
index 0000000..a33a964
--- /dev/null
+++ b/harness/src/main/java/com/android/csuite/core/PackageModuleInfoProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 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 com.android.csuite.core;
+
+import com.android.csuite.core.ModuleTemplate.ResourceLoader;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/** A module info provider that accepts package names and files that contains package names. */
+public final class PackageModuleInfoProvider implements ModuleInfoProvider {
+ @VisibleForTesting static final String PACKAGE = "package";
+ @VisibleForTesting static final String PACKAGE_PLACEHOLDER = "{package}";
+ @VisibleForTesting static final String TEMPLATE = "template";
+
+ @Option(
+ name = TEMPLATE,
+ description = "Module config template resource path.",
+ importance = Importance.ALWAYS)
+ private String mTemplate;
+
+ @Option(name = PACKAGE, description = "App package names.")
+ private final Set<String> mPackages = new HashSet<>();
+
+ private final ResourceLoader mResourceLoader;
+
+ public PackageModuleInfoProvider() {
+ this(new ModuleTemplate.ClassResourceLoader());
+ }
+
+ @VisibleForTesting
+ PackageModuleInfoProvider(ResourceLoader resourceLoader) {
+ mResourceLoader = resourceLoader;
+ }
+
+ @Override
+ public Stream<ModuleInfoProvider.ModuleInfo> get() throws IOException {
+ ModuleTemplate moduleTemplate = ModuleTemplate.load(mTemplate, mResourceLoader);
+
+ return mPackages.stream()
+ .distinct()
+ .map(
+ packageName ->
+ new ModuleInfoProvider.ModuleInfo(
+ packageName,
+ moduleTemplate.substitute(
+ Map.of(PACKAGE_PLACEHOLDER, packageName))));
+ }
+}
diff --git a/harness/src/main/java/com/android/csuite/core/PackagesFileModuleInfoProvider.java b/harness/src/main/java/com/android/csuite/core/PackagesFileModuleInfoProvider.java
new file mode 100644
index 0000000..cf1de81
--- /dev/null
+++ b/harness/src/main/java/com/android/csuite/core/PackagesFileModuleInfoProvider.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 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 com.android.csuite.core;
+
+import com.android.csuite.core.ModuleTemplate.ResourceLoader;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/** A module info provider that accepts files that contains package names. */
+public final class PackagesFileModuleInfoProvider implements ModuleInfoProvider {
+ @VisibleForTesting static final String PACKAGES_FILE = "packages-file";
+ @VisibleForTesting static final String COMMENT_LINE_PREFIX = "#";
+ @VisibleForTesting static final String PACKAGE_PLACEHOLDER = "{package}";
+ @VisibleForTesting static final String TEMPLATE = "template";
+
+ @Option(
+ name = TEMPLATE,
+ description = "Module config template resource path.",
+ importance = Importance.ALWAYS)
+ private String mTemplate;
+
+ @Option(
+ name = PACKAGES_FILE,
+ description =
+ "File paths that contain package names separated by newline characters."
+ + " Comment lines are supported only if the lines start with double slash."
+ + " Trailing comments are not supported. Empty lines are ignored.")
+ private final Set<File> mPackagesFiles = new HashSet<>();
+
+ private final ResourceLoader mResourceLoader;
+
+ public PackagesFileModuleInfoProvider() {
+ this(new ModuleTemplate.ClassResourceLoader());
+ }
+
+ @VisibleForTesting
+ PackagesFileModuleInfoProvider(ResourceLoader resourceLoader) {
+ mResourceLoader = resourceLoader;
+ }
+
+ @Override
+ public Stream<ModuleInfoProvider.ModuleInfo> get() throws IOException {
+ ModuleTemplate moduleTemplate = ModuleTemplate.load(mTemplate, mResourceLoader);
+
+ return mPackagesFiles.stream()
+ .flatMap(
+ file -> {
+ try {
+ return Files.readAllLines(file.toPath()).stream();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ })
+ .map(String::trim)
+ .filter(PackagesFileModuleInfoProvider::isNotCommentLine)
+ .distinct()
+ .map(
+ packageName ->
+ new ModuleInfoProvider.ModuleInfo(
+ packageName,
+ moduleTemplate.substitute(
+ Map.of(PACKAGE_PLACEHOLDER, packageName))));
+ }
+
+ private static boolean isNotCommentLine(String text) {
+ // Check the text is not an empty string and not a comment line.
+ return !text.isEmpty() && !text.startsWith(COMMENT_LINE_PREFIX);
+ }
+}
diff --git a/harness/src/main/resources/config/csuite-base.xml b/harness/src/main/resources/config/csuite-base.xml
index 03e418b..3d39bb3 100644
--- a/harness/src/main/resources/config/csuite-base.xml
+++ b/harness/src/main/resources/config/csuite-base.xml
@@ -31,9 +31,7 @@
<target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer">
<option name="test-file-name" value="csuite-launch-instrumentation.apk"/>
</target_preparer>
- <!-- Cleans generated module files after test -->
- <target_preparer class="com.android.csuite.config.ModuleGenerator" />
- <object type="PACKAGE_NAME_PROVIDER" class="com.android.csuite.core.CommandLinePackageNameProvider" />
- <object type="PACKAGE_NAME_PROVIDER" class="com.android.csuite.core.FileBasedPackageNameProvider" />
+ <object type="MODULE_INFO_PROVIDER" class="com.android.csuite.core.PackageModuleInfoProvider" />
+ <object type="MODULE_INFO_PROVIDER" class="com.android.csuite.core.PackagesFileModuleInfoProvider" />
</configuration>
diff --git a/harness/src/test/java/com/android/csuite/CSuiteUnitTests.java b/harness/src/test/java/com/android/csuite/CSuiteUnitTests.java
index 6eb1103..485de56 100644
--- a/harness/src/test/java/com/android/csuite/CSuiteUnitTests.java
+++ b/harness/src/test/java/com/android/csuite/CSuiteUnitTests.java
@@ -25,10 +25,11 @@ import org.junit.runners.Suite.SuiteClasses;
com.android.compatibility.targetprep.CheckGmsPreparerTest.class,
com.android.compatibility.testtype.AppLaunchTestTest.class,
com.android.csuite.config.AppRemoteFileResolverTest.class,
- com.android.csuite.config.ModuleGeneratorTest.class,
- com.android.csuite.core.CommandLinePackageNameProviderTest.class,
- com.android.csuite.core.FileBasedPackageNameProviderTest.class,
+ com.android.csuite.core.ModuleGeneratorTest.class,
+ com.android.csuite.core.PackageModuleInfoProviderTest.class,
+ com.android.csuite.core.PackagesFileModuleInfoProviderTest.class,
com.android.csuite.core.SystemAppUninstallerTest.class,
+ com.android.csuite.core.ModuleTemplateTest.class,
com.android.csuite.testing.CorrespondencesTest.class,
com.android.csuite.testing.MoreAssertsTest.class,
})
diff --git a/harness/src/test/java/com/android/csuite/config/ModuleGeneratorTest.java b/harness/src/test/java/com/android/csuite/config/ModuleGeneratorTest.java
deleted file mode 100644
index a679828..0000000
--- a/harness/src/test/java/com/android/csuite/config/ModuleGeneratorTest.java
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright (C) 2020 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 com.android.csuite.config;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import com.android.csuite.core.PackageNameProvider;
-import com.android.tradefed.build.BuildInfo;
-import com.android.tradefed.config.Configuration;
-import com.android.tradefed.config.IConfiguration;
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.invoker.InvocationContext;
-import com.android.tradefed.invoker.TestInformation;
-
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ListMultimap;
-import com.google.common.jimfs.Jimfs;
-import com.google.common.truth.IterableSubject;
-import com.google.common.truth.StringSubject;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-@RunWith(JUnit4.class)
-public final class ModuleGeneratorTest {
- private static final String TEST_PACKAGE_NAME1 = "test.package.name1";
- private static final String TEST_PACKAGE_NAME2 = "test.package.name2";
- private static final String PACKAGE_PLACEHOLDER = "{package}";
- private static final Exception NO_EXCEPTION = null;
-
- private final FileSystem mFileSystem =
- Jimfs.newFileSystem(com.google.common.jimfs.Configuration.unix());
-
- @Test
- public void tearDown_packageNamesProvided_deletesGeneratedModules() throws Exception {
- Path testsDir = createTestsDir();
- ModuleGenerator generator1 =
- createGeneratorBuilder()
- .setTestsDir(testsDir)
- .addPackage(TEST_PACKAGE_NAME1)
- .addPackage(TEST_PACKAGE_NAME2)
- .build();
- generator1.split();
- ModuleGenerator generator2 =
- createGeneratorBuilder()
- .setTestsDir(testsDir)
- .addPackage(TEST_PACKAGE_NAME1)
- .addPackage(TEST_PACKAGE_NAME2)
- .build();
-
- generator2.tearDown(createTestInfo(), NO_EXCEPTION);
-
- assertThatListDirectory(testsDir).isEmpty();
- }
-
- @Test
- public void tearDown_packageNamesNotProvided_doesNotThrowError() throws Exception {
- ModuleGenerator generator = createGeneratorBuilder().setTestsDir(createTestsDir()).build();
- generator.split();
-
- generator.tearDown(createTestInfo(), NO_EXCEPTION);
- }
-
- @Test
- public void split_packageNameIsEmptyString_throwsError() throws Exception {
- ModuleGenerator generator = createGeneratorBuilder().addPackage("").build();
-
- assertThrows(IllegalArgumentException.class, () -> generator.split());
- }
-
- @Test
- public void split_packageNameContainsPlaceholder_throwsError() throws Exception {
- ModuleGenerator generator =
- createGeneratorBuilder().addPackage("a" + PACKAGE_PLACEHOLDER + "b").build();
-
- assertThrows(IllegalArgumentException.class, () -> generator.split());
- }
-
- @Test
- public void split_multiplePackageNameProviders_generateModulesForAll() throws Exception {
- Path testsDir = createTestsDir();
- ModuleGenerator generator =
- createGeneratorBuilder()
- .setTestsDir(testsDir)
- .addPackageNameProvider(() -> ImmutableSet.of(TEST_PACKAGE_NAME1))
- // Simulates package providers providing duplicated package names.
- .addPackageNameProvider(() -> ImmutableSet.of(TEST_PACKAGE_NAME1))
- .addPackageNameProvider(() -> ImmutableSet.of(TEST_PACKAGE_NAME2))
- .build();
-
- generator.split();
-
- assertThatListDirectory(testsDir)
- .containsExactly(
- getModuleConfigFile(testsDir, TEST_PACKAGE_NAME1),
- getModuleConfigFile(testsDir, TEST_PACKAGE_NAME2));
- }
-
- @Test
- public void split_packageNameProviderThrowsException_throwsException() throws Exception {
- Path testsDir = createTestsDir();
- ModuleGenerator generator =
- createGeneratorBuilder()
- .setTestsDir(testsDir)
- .addPackageNameProvider(
- () -> {
- throw new IOException();
- })
- .build();
-
- assertThrows(UncheckedIOException.class, () -> generator.split());
- }
-
- @Test
- public void split_packageNamesNotProvided_doesNotGenerate() throws Exception {
- Path testsDir = createTestsDir();
- ModuleGenerator generator = createGeneratorBuilder().setTestsDir(testsDir).build();
-
- generator.split();
-
- assertThatListDirectory(testsDir).isEmpty();
- }
-
- @Test
- public void split_templateContainsPlaceholders_replacesPlaceholdersInOutput() throws Exception {
- Path testsDir = createTestsDir();
- String content = "hello placeholder%s%s world";
- ModuleGenerator generator =
- createGeneratorBuilder()
- .setTestsDir(testsDir)
- .addPackage(TEST_PACKAGE_NAME1)
- .addPackage(TEST_PACKAGE_NAME2)
- .setTemplateContent(
- String.format(content, PACKAGE_PLACEHOLDER, PACKAGE_PLACEHOLDER))
- .build();
-
- generator.split();
-
- assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME1)
- .isEqualTo(String.format(content, TEST_PACKAGE_NAME1, TEST_PACKAGE_NAME1));
- assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME2)
- .isEqualTo(String.format(content, TEST_PACKAGE_NAME2, TEST_PACKAGE_NAME2));
- }
-
- @Test
- public void split_templateDoesNotContainPlaceholder_outputsTemplateContent() throws Exception {
- Path testsDir = createTestsDir();
- String content = "no placeholder";
- ModuleGenerator generator =
- createGeneratorBuilder()
- .setTestsDir(testsDir)
- .addPackage(TEST_PACKAGE_NAME1)
- .addPackage(TEST_PACKAGE_NAME2)
- .setTemplateContent(content)
- .build();
-
- generator.split();
-
- assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME1).isEqualTo(content);
- assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME2).isEqualTo(content);
- }
-
- @Test
- public void split_templateContentIsEmpty_outputsTemplateContent() throws Exception {
- Path testsDir = createTestsDir();
- String content = "";
- ModuleGenerator generator =
- createGeneratorBuilder()
- .setTestsDir(testsDir)
- .addPackage(TEST_PACKAGE_NAME1)
- .addPackage(TEST_PACKAGE_NAME2)
- .setTemplateContent(content)
- .build();
-
- generator.split();
-
- assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME1).isEqualTo(content);
- assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME2).isEqualTo(content);
- }
-
- private static StringSubject assertThatModuleConfigFileContent(
- Path testsDir, String packageName) throws IOException {
- return assertThat(
- new String(Files.readAllBytes(getModuleConfigFile(testsDir, packageName))));
- }
-
- private static IterableSubject assertThatListDirectory(Path dir) throws IOException {
- // Convert stream to list because com.google.common.truth.Truth8 is not available.
- return assertThat(
- Files.walk(dir)
- .filter(p -> !p.equals(dir))
- .collect(ImmutableList.toImmutableList()));
- }
-
- private static Path getModuleConfigFile(Path baseDir, String packageName) {
- return baseDir.resolve(packageName + ".config");
- }
-
- private Path createTestsDir() throws IOException {
- Path rootPath = mFileSystem.getPath("csuite");
- Files.createDirectories(rootPath);
- return Files.createTempDirectory(rootPath, "testDir");
- }
-
- private static TestInformation createTestInfo() {
- IInvocationContext context = new InvocationContext();
- context.addAllocatedDevice("device1", Mockito.mock(ITestDevice.class));
- context.addDeviceBuildInfo("device1", new BuildInfo());
- return TestInformation.newBuilder().setInvocationContext(context).build();
- }
-
- private GeneratorBuilder createGeneratorBuilder() throws IOException {
- return new GeneratorBuilder()
- .setFileSystem(mFileSystem)
- .setTemplateContent(MODULE_TEMPLATE_CONTENT)
- .setOption(ModuleGenerator.OPTION_TEMPLATE, "empty_path");
- }
-
- private static final class GeneratorBuilder {
- private final ListMultimap<String, String> mOptions = ArrayListMultimap.create();
- private final Set<String> mPackages = new HashSet<>();
- private final List<PackageNameProvider> mPackageNameProviders = new ArrayList<>();
- private Path mTestsDir;
- private String mTemplateContent;
- private FileSystem mFileSystem;
-
- GeneratorBuilder addPackage(String packageName) {
- mPackages.add(packageName);
- return this;
- }
-
- GeneratorBuilder addPackageNameProvider(PackageNameProvider packageNameProvider) {
- mPackageNameProviders.add(packageNameProvider);
- return this;
- }
-
- GeneratorBuilder setFileSystem(FileSystem fileSystem) {
- mFileSystem = fileSystem;
- return this;
- }
-
- GeneratorBuilder setTemplateContent(String templateContent) {
- mTemplateContent = templateContent;
- return this;
- }
-
- GeneratorBuilder setTestsDir(Path testsDir) {
- mTestsDir = testsDir;
- return this;
- }
-
- GeneratorBuilder setOption(String key, String value) {
- mOptions.put(key, value);
- return this;
- }
-
- ModuleGenerator build() throws Exception {
- ModuleGenerator generator =
- new ModuleGenerator(
- mFileSystem, buildInfo -> mTestsDir, resourcePath -> mTemplateContent);
-
- OptionSetter optionSetter = new OptionSetter(generator);
- for (Map.Entry<String, String> entry : mOptions.entries()) {
- optionSetter.setOptionValue(entry.getKey(), entry.getValue());
- }
-
- List<PackageNameProvider> packageNameProviders = new ArrayList<>(mPackageNameProviders);
- packageNameProviders.add(() -> mPackages);
-
- IConfiguration configuration = new Configuration("name", "description");
- configuration.setConfigurationObjectList(
- ModuleGenerator.PACKAGE_NAME_PROVIDER, packageNameProviders);
- generator.setConfiguration(configuration);
-
- return generator;
- }
- }
-
- private static final String MODULE_TEMPLATE_CONTENT =
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<configuration description=\"description\">\n"
- + " <option name=\"package-name\" value=\"{package}\"/>\n"
- + " <target_generator class=\"some.generator.class\">\n"
- + " <option name=\"test-file-name\" value=\"app://{package}\"/>\n"
- + " </target_generator>\n"
- + " <test class=\"some.test.class\"/>\n"
- + "</configuration>";
-}
diff --git a/harness/src/test/java/com/android/csuite/core/CommandLinePackageNameProviderTest.java b/harness/src/test/java/com/android/csuite/core/CommandLinePackageNameProviderTest.java
deleted file mode 100644
index 41e6dc1..0000000
--- a/harness/src/test/java/com/android/csuite/core/CommandLinePackageNameProviderTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 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 com.android.csuite.core;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.android.tradefed.config.OptionSetter;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Set;
-
-@RunWith(JUnit4.class)
-public final class CommandLinePackageNameProviderTest {
-
- @Test
- public void get_packageNamesProvided_returnsPackageNames() throws Exception {
- CommandLinePackageNameProvider provider = new CommandLinePackageNameProvider();
- String package1 = "package.name1";
- String package2 = "package.name2";
- OptionSetter optionSetter = new OptionSetter(provider);
- optionSetter.setOptionValue(CommandLinePackageNameProvider.PACKAGE, package1);
- optionSetter.setOptionValue(CommandLinePackageNameProvider.PACKAGE, package2);
-
- Set<String> packageNames = provider.get();
-
- assertThat(packageNames).containsExactly(package1, package2);
- }
-}
diff --git a/harness/src/test/java/com/android/csuite/core/FileBasedPackageNameProviderTest.java b/harness/src/test/java/com/android/csuite/core/FileBasedPackageNameProviderTest.java
deleted file mode 100644
index ee429b2..0000000
--- a/harness/src/test/java/com/android/csuite/core/FileBasedPackageNameProviderTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2020 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 com.android.csuite.core;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.android.tradefed.config.ConfigurationException;
-import com.android.tradefed.config.OptionSetter;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Set;
-
-@RunWith(JUnit4.class)
-public final class FileBasedPackageNameProviderTest {
- private static final String TEST_PACKAGE_NAME1 = "test.package.name1";
- private static final String TEST_PACKAGE_NAME2 = "test.package.name2";
- private static final String PACKAGE_PLACEHOLDER = "{package}";
- private static final Exception NO_EXCEPTION = null;
-
- @Rule public final TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Test
- public void get_fileNotSpecified_returnsEmptySet() throws Exception {
- FileBasedPackageNameProvider provider = createProvider();
-
- Set<String> packageNames = provider.get();
-
- assertThat(packageNames).isEmpty();
- }
-
- @Test
- public void get_multipleFileSpecified_returnsAllEntries() throws Exception {
- String packageName1 = "a";
- String packageName2 = "b";
- String packageName3 = "c";
- String packageName4 = "d";
- FileBasedPackageNameProvider provider =
- createProvider(
- createPackagesFile(packageName1 + "\n" + packageName2),
- createPackagesFile(packageName3 + "\n" + packageName4));
-
- Set<String> packageNames = provider.get();
-
- assertThat(packageNames)
- .containsExactly(packageName1, packageName2, packageName3, packageName4);
- }
-
- @Test
- public void get_fileContainsEmptyLines_ignoresEmptyLines() throws Exception {
- String packageName1 = "a";
- String packageName2 = "b";
- FileBasedPackageNameProvider provider =
- createProvider(createPackagesFile(packageName1 + "\n \n\n" + packageName2));
-
- Set<String> packageNames = provider.get();
-
- assertThat(packageNames).containsExactly(packageName1, packageName2);
- }
-
- @Test
- public void get_fileContainsCommentLines_ignoresCommentLines() throws Exception {
- String packageName1 = "a";
- String packageName2 = "b";
- FileBasedPackageNameProvider provider =
- createProvider(
- createPackagesFile(
- packageName1
- + "\n"
- + FileBasedPackageNameProvider.COMMENT_LINE_PREFIX
- + " Some comments\n"
- + packageName2));
-
- Set<String> packageNames = provider.get();
-
- assertThat(packageNames).containsExactly(packageName1, packageName2);
- }
-
- private FileBasedPackageNameProvider createProvider(Path... packagesFiles)
- throws IOException, ConfigurationException {
- FileBasedPackageNameProvider provider = new FileBasedPackageNameProvider();
- OptionSetter optionSetter = new OptionSetter(provider);
- for (Path packagesFile : packagesFiles) {
- optionSetter.setOptionValue(
- FileBasedPackageNameProvider.PACKAGES_FILE, packagesFile.toString());
- }
- return provider;
- }
-
- private Path createPackagesFile(String content) throws IOException {
- Path tempFile = Files.createTempFile(tempFolder.getRoot().toPath(), "packages", ".txt");
- Files.write(tempFile, content.getBytes());
- return tempFile;
- }
-}
diff --git a/harness/src/test/java/com/android/csuite/core/ModuleGeneratorTest.java b/harness/src/test/java/com/android/csuite/core/ModuleGeneratorTest.java
new file mode 100644
index 0000000..c785a42
--- /dev/null
+++ b/harness/src/test/java/com/android/csuite/core/ModuleGeneratorTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2020 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 com.android.csuite.core;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.config.Configuration;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.invoker.TestInformation;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.jimfs.Jimfs;
+import com.google.common.truth.IterableSubject;
+import com.google.common.truth.StringSubject;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+
+@RunWith(JUnit4.class)
+public final class ModuleGeneratorTest {
+ private static final String TEST_PACKAGE_NAME1 = "test.package.name1";
+ private static final String TEST_PACKAGE_NAME2 = "test.package.name2";
+ private static final String TEST_PACKAGE_NAME3 = "test.package.name3";
+ private static final String PACKAGE_PLACEHOLDER = "{package}";
+ private static final Exception NO_EXCEPTION = null;
+
+ private final FileSystem mFileSystem =
+ Jimfs.newFileSystem(com.google.common.jimfs.Configuration.unix());
+
+ @Test
+ public void tearDown_nonModuleFilesExist_doesNotDeleteNonModules() throws Exception {
+ Path testsDir = createTestsDir();
+ Path nonModule = Files.createFile(testsDir.resolve("a"));
+ Files.createFile(testsDir.resolve("b" + ModuleGenerator.MODULE_FILE_NAME_EXTENSION));
+ ModuleGenerator generator = new GeneratorBuilder().setTestsDir(testsDir).build();
+
+ generator.tearDown(createTestInfo(), NO_EXCEPTION);
+
+ assertThatListDirectory(testsDir).containsExactly(nonModule);
+ }
+
+ @Test
+ public void tearDown_packageNamesProvided_deletesGeneratedModules() throws Exception {
+ Path testsDir = createTestsDir();
+ ModuleGenerator generator1 =
+ new GeneratorBuilder()
+ .setTestsDir(testsDir)
+ .addModuleInfoProvider(
+ () ->
+ Stream.of(
+ new ModuleInfoProvider.ModuleInfo(
+ TEST_PACKAGE_NAME1, "")))
+ .addModuleInfoProvider(
+ () ->
+ Stream.of(
+ new ModuleInfoProvider.ModuleInfo(
+ TEST_PACKAGE_NAME2, "")))
+ .build();
+ generator1.split();
+ ModuleGenerator generator2 = new GeneratorBuilder().setTestsDir(testsDir).build();
+
+ generator2.tearDown(createTestInfo(), NO_EXCEPTION);
+
+ assertThatListDirectory(testsDir).isEmpty();
+ }
+
+ @Test
+ public void tearDown_moduleInfoNotProvided_doesNotThrowError() throws Exception {
+ ModuleGenerator generator = new GeneratorBuilder().setTestsDir(createTestsDir()).build();
+ generator.split();
+
+ generator.tearDown(createTestInfo(), NO_EXCEPTION);
+ }
+
+ @Test
+ public void split_moduleInfoStreamProvided_streamIsClosed() throws Exception {
+ AtomicBoolean wasClosed = new AtomicBoolean(false);
+ ModuleGenerator generator =
+ new GeneratorBuilder()
+ .setTestsDir(createTestsDir())
+ .addModuleInfoProvider(
+ () ->
+ Stream.of(
+ new ModuleInfoProvider.ModuleInfo(
+ TEST_PACKAGE_NAME1, ""))
+ .onClose(() -> wasClosed.set(true)))
+ .build();
+
+ generator.split();
+
+ assertThat(wasClosed.get()).isTrue();
+ }
+
+ @Test
+ public void split_moduleInfoProvidersSpecified_contentIsWritten() throws Exception {
+ Path testsDir = createTestsDir();
+ String content1 = "a";
+ String content2 = "b";
+ ModuleGenerator generator =
+ new GeneratorBuilder()
+ .setTestsDir(testsDir)
+ .addModuleInfoProvider(
+ () ->
+ Stream.of(
+ new ModuleInfoProvider.ModuleInfo(
+ TEST_PACKAGE_NAME1, content1)))
+ .addModuleInfoProvider(
+ () ->
+ Stream.of(
+ new ModuleInfoProvider.ModuleInfo(
+ TEST_PACKAGE_NAME2, content2)))
+ .build();
+
+ generator.split();
+
+ assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME1).isEqualTo(content1);
+ assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME2).isEqualTo(content2);
+ }
+
+ @Test
+ public void split_emptyModuleNameProvided_throwsException() throws Exception {
+ Path testsDir = createTestsDir();
+ ModuleGenerator generator =
+ new GeneratorBuilder()
+ .setTestsDir(testsDir)
+ .addModuleInfoProvider(
+ () -> Stream.of(new ModuleInfoProvider.ModuleInfo(" ", "a")))
+ .build();
+
+ assertThrows(IllegalArgumentException.class, () -> generator.split());
+ }
+
+ @Test
+ public void split_duplicatedModuleNamesProvided_throwsException() throws Exception {
+ Path testsDir = createTestsDir();
+ ModuleGenerator generator =
+ new GeneratorBuilder()
+ .setTestsDir(testsDir)
+ .addModuleInfoProvider(
+ () ->
+ Stream.of(
+ new ModuleInfoProvider.ModuleInfo(
+ TEST_PACKAGE_NAME1, "a")))
+ .addModuleInfoProvider(
+ () ->
+ Stream.of(
+ new ModuleInfoProvider.ModuleInfo(
+ TEST_PACKAGE_NAME1, "b")))
+ .build();
+
+ assertThrows(IllegalArgumentException.class, () -> generator.split());
+ }
+
+ @Test
+ public void split_moduleInfoProvidersSpecified_generateModulesForAll() throws Exception {
+ Path testsDir = createTestsDir();
+ ModuleGenerator generator =
+ new GeneratorBuilder()
+ .setTestsDir(testsDir)
+ .addModuleInfoProvider(
+ () ->
+ Arrays.asList(
+ new ModuleInfoProvider.ModuleInfo(
+ TEST_PACKAGE_NAME1, ""),
+ new ModuleInfoProvider.ModuleInfo(
+ TEST_PACKAGE_NAME2, ""))
+ .stream())
+ .addModuleInfoProvider(
+ () ->
+ Stream.of(
+ new ModuleInfoProvider.ModuleInfo(
+ TEST_PACKAGE_NAME3, "")))
+ .build();
+
+ generator.split();
+
+ assertThatListDirectory(testsDir)
+ .containsExactly(
+ getModuleConfigFile(testsDir, TEST_PACKAGE_NAME1),
+ getModuleConfigFile(testsDir, TEST_PACKAGE_NAME2),
+ getModuleConfigFile(testsDir, TEST_PACKAGE_NAME3));
+ }
+
+ @Test
+ public void split_streamThrowsException_throwsException() throws Exception {
+ ModuleGenerator generator =
+ new GeneratorBuilder()
+ .setTestsDir(createTestsDir())
+ .addModuleInfoProvider(
+ () ->
+ Arrays.stream(new String[] {"a"})
+ .map(
+ i -> {
+ throw new UncheckedIOException(
+ new IOException());
+ }))
+ .build();
+
+ assertThrows(UncheckedIOException.class, () -> generator.split());
+ }
+
+ @Test
+ public void split_providerThrowsException_throwsException() throws Exception {
+ ModuleGenerator generator =
+ new GeneratorBuilder()
+ .setTestsDir(createTestsDir())
+ .addModuleInfoProvider(
+ () -> {
+ throw new UncheckedIOException(new IOException());
+ })
+ .build();
+
+ assertThrows(UncheckedIOException.class, () -> generator.split());
+ }
+
+ @Test
+ public void split_noProviders_doesNotGenerate() throws Exception {
+ Path testsDir = createTestsDir();
+ ModuleGenerator generator = new GeneratorBuilder().setTestsDir(testsDir).build();
+
+ generator.split();
+
+ assertThatListDirectory(testsDir).isEmpty();
+ }
+
+ private static StringSubject assertThatModuleConfigFileContent(
+ Path testsDir, String packageName) throws IOException {
+ return assertThat(
+ new String(Files.readAllBytes(getModuleConfigFile(testsDir, packageName))));
+ }
+
+ private static IterableSubject assertThatListDirectory(Path dir) throws IOException {
+ // Convert stream to list because com.google.common.truth.Truth8 is not available.
+ return assertThat(
+ Files.walk(dir)
+ .filter(p -> !p.equals(dir))
+ .collect(ImmutableList.toImmutableList()));
+ }
+
+ private static Path getModuleConfigFile(Path baseDir, String packageName) {
+ return baseDir.resolve(packageName + ".config");
+ }
+
+ private Path createTestsDir() throws IOException {
+ Path rootPath = mFileSystem.getPath("csuite");
+ Files.createDirectories(rootPath);
+ return Files.createTempDirectory(rootPath, "testDir");
+ }
+
+ private static final class GeneratorBuilder {
+ private final List<ModuleInfoProvider> mModuleInfoProviders = new ArrayList<>();
+ private Path mTestsDir;
+
+ GeneratorBuilder addModuleInfoProvider(ModuleInfoProvider moduleInfoProviders) {
+ mModuleInfoProviders.add(moduleInfoProviders);
+ return this;
+ }
+
+ GeneratorBuilder setTestsDir(Path testsDir) {
+ mTestsDir = testsDir;
+ return this;
+ }
+
+ ModuleGenerator build() throws Exception {
+ ModuleGenerator generator = new ModuleGenerator(buildInfo -> mTestsDir);
+
+ IConfiguration configuration = new Configuration("name", "description");
+ configuration.setConfigurationObjectList(
+ ModuleGenerator.MODULE_INFO_PROVIDER, mModuleInfoProviders);
+ generator.setConfiguration(configuration);
+
+ return generator;
+ }
+ }
+
+ private static TestInformation createTestInfo() {
+ IInvocationContext context = new InvocationContext();
+ context.addAllocatedDevice("device1", Mockito.mock(ITestDevice.class));
+ context.addDeviceBuildInfo("device1", new BuildInfo());
+ return TestInformation.newBuilder().setInvocationContext(context).build();
+ }
+}
diff --git a/harness/src/test/java/com/android/csuite/core/ModuleTemplateTest.java b/harness/src/test/java/com/android/csuite/core/ModuleTemplateTest.java
new file mode 100644
index 0000000..2d7c700
--- /dev/null
+++ b/harness/src/test/java/com/android/csuite/core/ModuleTemplateTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 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 com.android.csuite.core;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Map;
+
+@RunWith(JUnit4.class)
+public final class ModuleTemplateTest {
+ @Test
+ public void substitute_multipleReplacementPairs_replaceAll() throws Exception {
+ String template = "-ab";
+ ModuleTemplate subject = createTestSubject(template);
+
+ String content = subject.substitute(Map.of("a", "c", "b", "d"));
+
+ assertThat(content).isEqualTo("-cd");
+ }
+
+ @Test
+ public void substitute_replacementKeyNotInTemplate_doesNotReplace() throws Exception {
+ String template = "-a";
+ ModuleTemplate subject = createTestSubject(template);
+
+ String content = subject.substitute(Map.of("b", ""));
+
+ assertThat(content).isEqualTo(template);
+ }
+
+ @Test
+ public void substitute_multipleReplacementKeyInTemplate_replaceTheKeys() throws Exception {
+ String template = "-aba";
+ ModuleTemplate subject = createTestSubject(template);
+
+ String content = subject.substitute(Map.of("a", "c"));
+
+ assertThat(content).isEqualTo("-cbc");
+ }
+
+ @Test
+ public void substitute_noReplacementPairs_returnTemplate() throws Exception {
+ String template = "-a";
+ ModuleTemplate subject = createTestSubject(template);
+
+ String content = subject.substitute(Map.of());
+
+ assertThat(content).isEqualTo(template);
+ }
+
+ @Test
+ public void substitute_templateContentIsEmpty_returnEmptyString() throws Exception {
+ String template = "";
+ ModuleTemplate subject = createTestSubject(template);
+
+ String content = subject.substitute(Map.of("a", "b"));
+
+ assertThat(content).isEqualTo(template);
+ }
+
+ private static ModuleTemplate createTestSubject(String templateContent) {
+ return new ModuleTemplate(templateContent);
+ }
+}
diff --git a/harness/src/test/java/com/android/csuite/core/PackageModuleInfoProviderTest.java b/harness/src/test/java/com/android/csuite/core/PackageModuleInfoProviderTest.java
new file mode 100644
index 0000000..754f4a2
--- /dev/null
+++ b/harness/src/test/java/com/android/csuite/core/PackageModuleInfoProviderTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 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 com.android.csuite.core;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.config.OptionSetter;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@RunWith(JUnit4.class)
+public final class PackageModuleInfoProviderTest {
+ @Rule public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Test
+ public void get_templateContainsPlaceholders_replacesPlaceholdersInOutput() throws Exception {
+ String content = "hello placeholder%s%s world";
+ String packageName1 = "a";
+ String packageName2 = "b";
+ PackageModuleInfoProvider provider =
+ createProviderBuilder()
+ .addPackage(packageName1)
+ .addPackage(packageName2)
+ .setTemplateContent(
+ String.format(
+ content,
+ PackagesFileModuleInfoProvider.PACKAGE_PLACEHOLDER,
+ PackagesFileModuleInfoProvider.PACKAGE_PLACEHOLDER))
+ .build();
+
+ Stream<ModuleInfoProvider.ModuleInfo> modulesInfo = provider.get();
+
+ assertThat(collectModuleContentStrings(modulesInfo))
+ .containsExactly(
+ String.format(content, packageName1, packageName1),
+ String.format(content, packageName2, packageName2));
+ }
+
+ @Test
+ public void get_containsDuplicatedPackageNames_ignoreDuplicates() throws Exception {
+ String packageName1 = "a";
+ String packageName2 = "b";
+ PackageModuleInfoProvider provider =
+ createProviderBuilder()
+ .addPackage(packageName1)
+ .addPackage(packageName1)
+ .addPackage(packageName2)
+ .build();
+
+ Stream<ModuleInfoProvider.ModuleInfo> modulesInfo = provider.get();
+
+ assertThat(collectModuleNames(modulesInfo)).containsExactly(packageName1, packageName2);
+ }
+
+ @Test
+ public void get_packageNamesProvided_returnsPackageNames() throws Exception {
+ String packageName1 = "a";
+ String packageName2 = "b";
+ PackageModuleInfoProvider provider =
+ createProviderBuilder().addPackage(packageName1).addPackage(packageName2).build();
+
+ Stream<ModuleInfoProvider.ModuleInfo> modulesInfo = provider.get();
+
+ assertThat(collectModuleNames(modulesInfo)).containsExactly(packageName1, packageName2);
+ }
+
+ private List<String> collectModuleContentStrings(
+ Stream<ModuleInfoProvider.ModuleInfo> modulesInfo) {
+ return modulesInfo
+ .map(ModuleInfoProvider.ModuleInfo::getContent)
+ .collect(Collectors.toList());
+ }
+
+ private List<String> collectModuleNames(Stream<ModuleInfoProvider.ModuleInfo> modulesInfo) {
+ return modulesInfo.map(ModuleInfoProvider.ModuleInfo::getName).collect(Collectors.toList());
+ }
+
+ private ProviderBuilder createProviderBuilder() {
+ return new ProviderBuilder().setTemplateContent(MODULE_TEMPLATE_CONTENT);
+ }
+
+ private static final class ProviderBuilder {
+ private final Set<String> mPackages = new HashSet<>();
+ private String mTemplateContent;
+
+ ProviderBuilder addPackage(String packageName) {
+ mPackages.add(packageName);
+ return this;
+ }
+
+ ProviderBuilder setTemplateContent(String templateContent) {
+ mTemplateContent = templateContent;
+ return this;
+ }
+
+ PackageModuleInfoProvider build() throws Exception {
+ PackageModuleInfoProvider provider =
+ new PackageModuleInfoProvider(resource -> mTemplateContent);
+
+ OptionSetter optionSetter = new OptionSetter(provider);
+ for (String p : mPackages) {
+ optionSetter.setOptionValue(PackageModuleInfoProvider.PACKAGE, p);
+ }
+ optionSetter.setOptionValue(PackageModuleInfoProvider.TEMPLATE, "empty");
+ return provider;
+ }
+ }
+
+ private static final String MODULE_TEMPLATE_CONTENT =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<configuration description=\"description\">\n"
+ + " <option name=\"package-name\" value=\"{package}\"/>\n"
+ + " <target_generator class=\"some.generator.class\">\n"
+ + " <option name=\"test-file-name\" value=\"app://{package}\"/>\n"
+ + " </target_generator>\n"
+ + " <test class=\"some.test.class\"/>\n"
+ + "</configuration>";
+}
diff --git a/harness/src/test/java/com/android/csuite/core/PackagesFileModuleInfoProviderTest.java b/harness/src/test/java/com/android/csuite/core/PackagesFileModuleInfoProviderTest.java
new file mode 100644
index 0000000..12ea012
--- /dev/null
+++ b/harness/src/test/java/com/android/csuite/core/PackagesFileModuleInfoProviderTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 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 com.android.csuite.core;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.config.OptionSetter;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@RunWith(JUnit4.class)
+public final class PackagesFileModuleInfoProviderTest {
+ @Rule public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Test
+ public void get_templateContainsPlaceholders_replacesPlaceholdersInOutput() throws Exception {
+ String content = "hello placeholder%s%s world";
+ String packageName1 = "a";
+ String packageName2 = "b";
+ PackagesFileModuleInfoProvider provider =
+ createProviderBuilder()
+ .addPackagesFile(createPackagesFile(packageName1 + "\n" + packageName2))
+ .setTemplateContent(
+ String.format(
+ content,
+ PackagesFileModuleInfoProvider.PACKAGE_PLACEHOLDER,
+ PackagesFileModuleInfoProvider.PACKAGE_PLACEHOLDER))
+ .build();
+
+ Stream<ModuleInfoProvider.ModuleInfo> modulesInfo = provider.get();
+
+ assertThat(collectModuleContentStrings(modulesInfo))
+ .containsExactly(
+ String.format(content, packageName1, packageName1),
+ String.format(content, packageName2, packageName2));
+ }
+
+ @Test
+ public void get_containsDuplicatedPackageNames_ignoreDuplicates() throws Exception {
+ String packageName1 = "a";
+ String packageName2 = "b";
+ PackagesFileModuleInfoProvider provider =
+ createProviderBuilder()
+ .addPackagesFile(createPackagesFile(packageName1 + "\n" + packageName1))
+ .addPackagesFile(createPackagesFile(packageName1 + "\n" + packageName2))
+ .build();
+
+ Stream<ModuleInfoProvider.ModuleInfo> modulesInfo = provider.get();
+
+ assertThat(collectModuleNames(modulesInfo)).containsExactly(packageName1, packageName2);
+ }
+
+ @Test
+ public void get_fileNotSpecified_returnsEmptySet() throws Exception {
+ PackagesFileModuleInfoProvider provider = createProviderBuilder().build();
+
+ Stream<ModuleInfoProvider.ModuleInfo> modulesInfo = provider.get();
+
+ assertThat(collectModuleNames(modulesInfo)).isEmpty();
+ }
+
+ @Test
+ public void get_multipleFileSpecified_returnsAllEntries() throws Exception {
+ String packageName1 = "a";
+ String packageName2 = "b";
+ String packageName3 = "c";
+ String packageName4 = "d";
+ PackagesFileModuleInfoProvider provider =
+ createProviderBuilder()
+ .addPackagesFile(createPackagesFile(packageName1 + "\n" + packageName2))
+ .addPackagesFile(createPackagesFile(packageName3 + "\n" + packageName4))
+ .build();
+
+ Stream<ModuleInfoProvider.ModuleInfo> modulesInfo = provider.get();
+
+ assertThat(collectModuleNames(modulesInfo))
+ .containsExactly(packageName1, packageName2, packageName3, packageName4);
+ }
+
+ @Test
+ public void get_fileContainsEmptyLines_ignoresEmptyLines() throws Exception {
+ String packageName1 = "a";
+ String packageName2 = "b";
+ PackagesFileModuleInfoProvider provider =
+ createProviderBuilder()
+ .addPackagesFile(
+ createPackagesFile(packageName1 + "\n \n\n" + packageName2))
+ .build();
+
+ Stream<ModuleInfoProvider.ModuleInfo> modulesInfo = provider.get();
+
+ assertThat(collectModuleNames(modulesInfo)).containsExactly(packageName1, packageName2);
+ }
+
+ @Test
+ public void get_fileContainsCommentLines_ignoresCommentLines() throws Exception {
+ String packageName1 = "a";
+ String packageName2 = "b";
+ PackagesFileModuleInfoProvider provider =
+ createProviderBuilder()
+ .addPackagesFile(
+ createPackagesFile(
+ packageName1
+ + "\n"
+ + PackagesFileModuleInfoProvider.COMMENT_LINE_PREFIX
+ + " Some comments\n"
+ + packageName2))
+ .build();
+
+ Stream<ModuleInfoProvider.ModuleInfo> modulesInfo = provider.get();
+
+ assertThat(collectModuleNames(modulesInfo)).containsExactly(packageName1, packageName2);
+ }
+
+ private List<String> collectModuleContentStrings(
+ Stream<ModuleInfoProvider.ModuleInfo> modulesInfo) {
+ return modulesInfo
+ .map(ModuleInfoProvider.ModuleInfo::getContent)
+ .collect(Collectors.toList());
+ }
+
+ private List<String> collectModuleNames(Stream<ModuleInfoProvider.ModuleInfo> modulesInfo) {
+ return modulesInfo.map(ModuleInfoProvider.ModuleInfo::getName).collect(Collectors.toList());
+ }
+
+ private Path createPackagesFile(String content) throws IOException {
+ Path tempFile = Files.createTempFile(tempFolder.getRoot().toPath(), "packages", ".txt");
+ Files.write(tempFile, content.getBytes());
+ return tempFile;
+ }
+
+ private ProviderBuilder createProviderBuilder() {
+ return new ProviderBuilder().setTemplateContent(MODULE_TEMPLATE_CONTENT);
+ }
+
+ private static final class ProviderBuilder {
+ private final Set<Path> mPackagesFiles = new HashSet<>();
+ private String mTemplateContent;
+
+ ProviderBuilder addPackagesFile(Path packagesFile) {
+ mPackagesFiles.add(packagesFile);
+ return this;
+ }
+
+ ProviderBuilder setTemplateContent(String templateContent) {
+ mTemplateContent = templateContent;
+ return this;
+ }
+
+ PackagesFileModuleInfoProvider build() throws Exception {
+ PackagesFileModuleInfoProvider provider =
+ new PackagesFileModuleInfoProvider(resource -> mTemplateContent);
+
+ OptionSetter optionSetter = new OptionSetter(provider);
+ for (Path p : mPackagesFiles) {
+ optionSetter.setOptionValue(
+ PackagesFileModuleInfoProvider.PACKAGES_FILE, p.toString());
+ }
+ optionSetter.setOptionValue(PackagesFileModuleInfoProvider.TEMPLATE, "empty");
+ return provider;
+ }
+ }
+
+ private static final String MODULE_TEMPLATE_CONTENT =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<configuration description=\"description\">\n"
+ + " <option name=\"package-name\" value=\"{package}\"/>\n"
+ + " <target_generator class=\"some.generator.class\">\n"
+ + " <option name=\"test-file-name\" value=\"app://{package}\"/>\n"
+ + " </target_generator>\n"
+ + " <test class=\"some.test.class\"/>\n"
+ + "</configuration>";
+}
diff --git a/tools/csuite_test/csuite_test.go b/tools/csuite_test/csuite_test.go
index 74373f2..11469df 100644
--- a/tools/csuite_test/csuite_test.go
+++ b/tools/csuite_test/csuite_test.go
@@ -121,9 +121,13 @@ const (
limitations under the License.
-->
<configuration>
- <test class="com.android.csuite.config.ModuleGenerator">
- <option name="template" value="{templatePath}" />
- </test>
+ <option name="template" value="{templatePath}" />
+
+ <!-- Generates module files in the beginning of the test. -->
+ <test class="com.android.csuite.core.ModuleGenerator" />
+ <!-- Cleans the generated module files after the test. -->
+ <target_preparer class="com.android.csuite.core.ModuleGenerator" />
+
<include name="csuite-base" />
<include name="{planInclude}" />
<option name="plan" value="{planName}" />