diff options
| author | Yuexi Ma <yuexima@google.com> | 2021-08-18 20:21:40 -0700 |
|---|---|---|
| committer | Yuexi Ma <yuexima@google.com> | 2021-08-23 23:04:19 -0700 |
| commit | b2f9e540a171a389d4cfe4ca7c3a49d7b798234b (patch) | |
| tree | b2d4068f1e62428eae11be5091cee7c810e0a2d6 | |
| parent | 63aebf250bc079f991a6b00b995287bb6649c78e (diff) | |
| download | platform_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
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><object type="PACKAGE_NAME_PROVIDER" class="</b><i>provider_class_name</i><b>"/></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><object type="MODULE_INFO_PROVIDER" class="</b><i>provider_class_name</i><b>"/></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}" /> |
