diff options
author | Android AppBundle <noreply@google.com> | 2017-08-17 15:30:34 +0100 |
---|---|---|
committer | Tom Dobek <tdobek@google.com> | 2017-08-17 15:37:17 +0100 |
commit | 7c394fa23d0e67a11e7da0bcb00e5c65affe4751 (patch) | |
tree | 6fb07636df456cfea2cfe6937ff226e5d9947f18 | |
parent | 48e13052143e05f264f53f9286b3cff861ef5aac (diff) | |
download | appbundle-7c394fa23d0e67a11e7da0bcb00e5c65affe4751.tar.gz |
Project import generated by Copybara.
PiperOrigin-RevId: 165576084
Change-Id: I5bc76c2688f5a9fa18ea9801308c8b8e690b1f66
14 files changed, 1515 insertions, 5 deletions
diff --git a/bundle2installable/.idea/scopes/scope_settings.xml b/bundle2installable/.idea/scopes/scope_settings.xml deleted file mode 100644 index 922003b..0000000 --- a/bundle2installable/.idea/scopes/scope_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<component name="DependencyValidationManager"> - <state> - <option name="SKIP_IMPORT_STATEMENTS" value="false" /> - </state> -</component>
\ No newline at end of file diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/AppBundle.java b/bundletool/java/com/android/tools/appbundle/bundletool/AppBundle.java new file mode 100644 index 0000000..cbdf4e1 --- /dev/null +++ b/bundletool/java/com/android/tools/appbundle/bundletool/AppBundle.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** Represents an app bundle. */ +public class AppBundle { + + private ZipFile bundleFile; + private Map<String, BundleModule> modules; + + public AppBundle(ZipFile bundleFile) { + this.bundleFile = bundleFile; + this.modules = new HashMap<>(); + open(); + } + + private void open() { + Map<String, BundleModule.Builder> moduleBuilders = new HashMap<>(); + Enumeration<? extends ZipEntry> entries = bundleFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + Path path = Paths.get(entry.getName()); + if (path.getNameCount() > 1) { + String moduleName = path.getName(0).toString(); + BundleModule.Builder moduleBuilder = + moduleBuilders.computeIfAbsent( + moduleName, name -> new BundleModule.Builder(name, this)); + moduleBuilder.addZipEntry(entry); + } + } + modules.putAll(Maps.transformValues(moduleBuilders, BundleModule.Builder::build)); + } + + public Map<String, BundleModule> getModules() { + return ImmutableMap.copyOf(modules); + } + + public BundleModule getModule(String moduleName) { + return modules.get(moduleName); + } + + public InputStream getEntryInputStream(ZipEntry entry) throws IOException { + return bundleFile.getInputStream(entry); + } +} diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/BuildModuleCommand.java b/bundletool/java/com/android/tools/appbundle/bundletool/BuildModuleCommand.java new file mode 100644 index 0000000..8e30d9a --- /dev/null +++ b/bundletool/java/com/android/tools/appbundle/bundletool/BuildModuleCommand.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.android.tools.appbundle.bundletool.utils.FlagParser; +import com.google.auto.value.AutoValue; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +/** Command responsible for building an App Bundle module. */ +@AutoValue +public abstract class BuildModuleCommand { + + public static final String COMMAND_NAME = "build-module"; + + private static final String OUTPUT_FLAG = "output"; + private static final String MANIFEST_FLAG = "manifest"; + private static final String MANIFEST_DIR_FLAG = "manifest-dir"; + private static final String DEX_FLAG = "dex"; + private static final String DEX_DIR_FLAG = "dex-dir"; + private static final String RESOURCES_DIR_FLAG = "resources-dir"; + private static final String ASSETS_DIR_FLAG = "assets-dir"; + private static final String NATIVE_DIR_FLAG = "native-dir"; + + abstract Path getOutputPath(); + + abstract Optional<Path> getManifestPath(); + + abstract Optional<Path> getManifestDirPath(); + + abstract Optional<Path> getDexPath(); + + abstract Optional<Path> getDexDirPath(); + + abstract Optional<Path> getResourcesDirPath(); + + abstract Optional<Path> getAssetsDirPath(); + + abstract Optional<Path> getNativeDirPath(); + + public static Builder builder() { + return new AutoValue_BuildModuleCommand.Builder(); + } + + /** Builder for the {@link BuildModuleCommand} */ + @AutoValue.Builder + public abstract static class Builder { + abstract Builder setOutputPath(Path outputPath); + + abstract Builder setManifestPath(Path manifestPath); + + abstract Builder setManifestDirPath(Path manifestDirPath); + + abstract Builder setDexPath(Path dexPath); + + abstract Builder setDexDirPath(Path dexDirPath); + + abstract Builder setResourcesDirPath(Path resourcesDirPath); + + abstract Builder setAssetsDirPath(Path assetsDirPath); + + abstract Builder setNativeDirPath(Path nativeDirPath); + + abstract BuildModuleCommand build(); + } + + static BuildModuleCommand fromFlags(FlagParser flagParser) { + Builder builder = + builder().setOutputPath(Paths.get(flagParser.getRequiredFlagValue(OUTPUT_FLAG))); + flagParser.getFlagValueAsPath(MANIFEST_FLAG).ifPresent(builder::setManifestPath); + flagParser.getFlagValueAsPath(MANIFEST_DIR_FLAG).ifPresent(builder::setManifestDirPath); + flagParser.getFlagValueAsPath(DEX_FLAG).ifPresent(builder::setDexPath); + flagParser.getFlagValueAsPath(DEX_DIR_FLAG).ifPresent(builder::setDexDirPath); + flagParser.getFlagValueAsPath(RESOURCES_DIR_FLAG).ifPresent(builder::setResourcesDirPath); + flagParser.getFlagValueAsPath(ASSETS_DIR_FLAG).ifPresent(builder::setAssetsDirPath); + flagParser.getFlagValueAsPath(NATIVE_DIR_FLAG).ifPresent(builder::setNativeDirPath); + + return builder.build(); + } + + public void execute() { + validateInput(); + + + } + + private void validateInput() { + checkArgument( + getManifestPath().isPresent() || getManifestDirPath().isPresent(), + "One of --%s or --%s is required.", + MANIFEST_FLAG, + MANIFEST_DIR_FLAG); + checkArgument( + !getManifestPath().isPresent() || !getManifestDirPath().isPresent(), + "Cannot set both --%s and --%s flags.", + MANIFEST_FLAG, + MANIFEST_DIR_FLAG); + checkArgument( + !getDexPath().isPresent() || !getDexDirPath().isPresent(), + "Cannot set both --%s and --%s flags.", + DEX_FLAG, + DEX_DIR_FLAG); + + checkArgument(!Files.exists(getOutputPath()), "File %s already exists.", getOutputPath()); + checkFileExistsAndReadable(getManifestPath()); + checkDirectoryExists(getManifestDirPath()); + checkFileExistsAndReadable(getDexPath()); + checkDirectoryExists(getDexDirPath()); + checkDirectoryExists(getResourcesDirPath()); + checkDirectoryExists(getAssetsDirPath()); + checkDirectoryExists(getNativeDirPath()); + } + + private static void checkFileExistsAndReadable(Optional<Path> pathOptional) { + if (pathOptional.isPresent()) { + Path path = pathOptional.get(); + checkArgument(Files.exists(path), "File '%s' was not found.", path); + checkArgument(Files.isReadable(path), "File '%s' is not readable.", path); + } + } + + private static void checkDirectoryExists(Optional<Path> pathOptional) { + if (pathOptional.isPresent()) { + Path path = pathOptional.get(); + checkArgument(Files.exists(path), "Directory '%s' was not found.", path); + checkArgument(Files.isDirectory(path), "'%s' is not a directory."); + } + } + + public static void help() { + System.out.println( + String.format( + "bundletool %s --output=<path/to/module.zip> " + + "[--%s=<path/to/AndroidManifest.flat>|--%s=<path/to/manifest-dir/>] " + + "[--%s=<path/to/classes.dex>|--%s=<path/to/dex-dir/>] " + + "[--%s=<path/to/res/>] " + + "[--%s=<path/to/assets/>] " + + "[--%s=<path/to/lib/>] ", + COMMAND_NAME, + MANIFEST_FLAG, + MANIFEST_DIR_FLAG, + DEX_FLAG, + DEX_DIR_FLAG, + RESOURCES_DIR_FLAG, + ASSETS_DIR_FLAG, + NATIVE_DIR_FLAG)); + System.out.println(); + System.out.println( + "Builds a module as a zip from an app's project. Note that the resources and the " + + "AndroidManifest.xml must already have been compiled with aapt2."); + System.out.println(); + System.out.println("--output: Path to the zip file to build."); + System.out.printf( + "--%s: Path to the AndroidManifest.flat compiled by aapt2. Use --%s if there " + + "are more than one.\n", + MANIFEST_FLAG, MANIFEST_DIR_FLAG); + System.out.printf( + "--%s: Path to the directory containing multiple Android manifests compiled by aapt2. " + + "A file named 'manifest-targeting.xml' must be present in the directory " + + "describing the targeting of each manifest present.\n", + MANIFEST_DIR_FLAG); + System.out.printf( + "--%s: Path to the dex file. Use --%s if there are more than one.\n", + DEX_FLAG, DEX_DIR_FLAG); + System.out.printf( + "--%s: Path to the directory containing multiple dex files. Unless all dex files must " + + "be included in the generated APKs (for MultiDex), a file named " + + "'dex-targeting.xml' must be present in the directory describing the targeting " + + "of the different dex files.\n", + DEX_DIR_FLAG); + System.out.printf( + "--%s: Path to the directory containing the resources file(s). A file named " + + "'resources.flat' must be present in that directory corresponding to the output " + + "of the aapt2 compilation of the resources.\n", + RESOURCES_DIR_FLAG); + System.out.printf("--%s: Path to the directory containing the assets.\n", ASSETS_DIR_FLAG); + System.out.printf( + "--%s: Path to the directory containing the native libraries.\n", NATIVE_DIR_FLAG); + } +} diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/BundleModule.java b/bundletool/java/com/android/tools/appbundle/bundletool/BundleModule.java new file mode 100644 index 0000000..c88d287 --- /dev/null +++ b/bundletool/java/com/android/tools/appbundle/bundletool/BundleModule.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool; + +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; + +/** Represents a single module inside App Bundle. */ +public class BundleModule { + + private AppBundle parent; + private String name; + private List<ZipEntry> entries; + + private BundleModule(String name, AppBundle parent, List<ZipEntry> entries) { + this.parent = parent; + this.name = name; + this.entries = entries; + } + + public String getName() { + return name; + } + + public AppBundle getParent() { + return parent; + } + + public List<ZipEntry> getEntries() { + return ImmutableList.copyOf(entries); + } + + /** Builder for BundleModule. */ + public static class Builder { + private List<ZipEntry> entries; + private String name; + private AppBundle parent; + + public Builder(String name, AppBundle parent) { + this.name = name; + this.parent = parent; + this.entries = new ArrayList<>(); + } + + public Builder addZipEntry(ZipEntry entry) { + this.entries.add(entry); + return this; + } + + public BundleModule build() { + return new BundleModule(name, parent, entries); + } + } +} diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/BundleToolMain.java b/bundletool/java/com/android/tools/appbundle/bundletool/BundleToolMain.java new file mode 100644 index 0000000..330a991 --- /dev/null +++ b/bundletool/java/com/android/tools/appbundle/bundletool/BundleToolMain.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool; + +import com.android.tools.appbundle.bundletool.utils.FlagParser; +import java.io.IOException; +import java.util.List; + +/** + * Main entry point of the bundle tool. + * + * <p>Consider running with -Dsun.zip.disableMemoryMapping when dealing with large bundles. + */ +public class BundleToolMain { + + public static final String LINK_CMD = "link"; + public static final String HELP_CMD = "help"; + + /** Parses the flags and routes to the appropriate command handler */ + public static void main(String[] args) throws IOException { + + FlagParser flagParser = new FlagParser(); + try { + flagParser.parse(args); + } catch (FlagParser.ParseException e) { + System.out.println(String.format("Error while parsing the flags: %s", e.getMessage())); + return; + } + List<String> commands = flagParser.getCommands(); + + if (commands.isEmpty()) { + System.out.println("Error: you have to specify a command"); + help(); + return; + } + + try { + switch (commands.get(0)) { + case BuildModuleCommand.COMMAND_NAME: + BuildModuleCommand.fromFlags(flagParser).execute(); + break; + case SplitModuleCommand.COMMAND_NAME: + new SplitModuleCommand(flagParser).execute(); + break; + case LINK_CMD: + throw new UnsupportedOperationException("Not implemented."); + case HELP_CMD: + if (commands.size() > 1) { + help(commands.get(1)); + } else { + help(); + } + return; + default: + System.out.println("Error: unrecognized command."); + help(); + } + } catch (Exception e) { + System.out.println("Error: " + e.getMessage()); + } + } + + /** Displays a general help. */ + public static void help() { + System.out.println( + String.format( + "bundletool [%s|%s|%s|%s] ...", + BuildModuleCommand.COMMAND_NAME, SplitModuleCommand.COMMAND_NAME, LINK_CMD, HELP_CMD)); + System.out.println("Type: bundletool help [command] to learn more about a given command."); + } + + /** Displays help about a given command. */ + public static void help(String commandName) { + switch (commandName) { + case BuildModuleCommand.COMMAND_NAME: + BuildModuleCommand.help(); + break; + case SplitModuleCommand.COMMAND_NAME: + SplitModuleCommand.help(); + break; + case LINK_CMD: + System.out.println("Help is not yet available."); + break; + default: + System.out.println("Unrecognized command."); + help(); + break; + } + } +} diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/Command.java b/bundletool/java/com/android/tools/appbundle/bundletool/Command.java new file mode 100644 index 0000000..670c0dc --- /dev/null +++ b/bundletool/java/com/android/tools/appbundle/bundletool/Command.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool; + +/** Interface for command implementations. */ +public interface Command { + + void execute() throws ExecutionException; + + /** Error indicating something went wrong during executing the command. */ + class ExecutionException extends RuntimeException { + + public ExecutionException(String message) { + super(message); + } + + public ExecutionException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/SplitModuleCommand.java b/bundletool/java/com/android/tools/appbundle/bundletool/SplitModuleCommand.java new file mode 100644 index 0000000..f6909af --- /dev/null +++ b/bundletool/java/com/android/tools/appbundle/bundletool/SplitModuleCommand.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool; + +import com.android.tools.appbundle.bundletool.utils.FlagParser; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +/** Implementation of the command to generate module splits. */ +public class SplitModuleCommand implements Command { + + private final String bundleLocation; + private final String outputDirectory; + private final String moduleName; + + public static final String COMMAND_NAME = "split-module"; + + private static final String BUNDLE_LOCATION_FLAG = "bundle"; + private static final String OUTPUT_DIRECTORY_FLAG = "output"; + private static final String MODULE_FLAG = "module"; + + public SplitModuleCommand(FlagParser parsedFlags) { + bundleLocation = parsedFlags.getRequiredFlagValue(BUNDLE_LOCATION_FLAG); + outputDirectory = parsedFlags.getRequiredFlagValue(OUTPUT_DIRECTORY_FLAG); + moduleName = parsedFlags.getRequiredFlagValue(MODULE_FLAG); + } + + @Override + public void execute() throws ExecutionException { + try { + AppBundle appBundle = new AppBundle(new ZipFile(bundleLocation)); + BundleModule module = appBundle.getModule(moduleName); + if (module == null) { + throw new ExecutionException( + String.format("Cannot find the %s module in the bundle", moduleName)); + } + splitModule(moduleName, module, outputDirectory); + } catch (ZipException e) { + throw new ExecutionException("Zip error while opening the bundle " + e.getMessage(), e); + } catch (FileNotFoundException e) { + throw new ExecutionException("Bundle file not found", e); + } catch (IOException e) { + throw new ExecutionException("I/O error while processing the bundle " + e.getMessage(), e); + } + } + + private void splitModule(String moduleName, BundleModule module, String outputDirectory) { + throw new UnsupportedOperationException("Not implemented"); + } + + public static void help() { + System.out.printf( + "bundletool %s --%s=[bundle.zip] --%s=[module-name] --%s=[output-dir]\n", + BUNDLE_LOCATION_FLAG, MODULE_FLAG, OUTPUT_DIRECTORY_FLAG, COMMAND_NAME); + System.out.println("Generates module splits for the given module of the bundle."); + System.out.println("For now, one split is generated containing all module's resources."); + System.out.println(); + System.out.printf("--%s: the zip file containing an App Bundle.\n", BUNDLE_LOCATION_FLAG); + System.out.printf("--%s: module for which generate the splits.\n", MODULE_FLAG); + System.out.printf( + "--%s: the directory where the module zip files should be written to.\n", + OUTPUT_DIRECTORY_FLAG); + } +} diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/utils/FlagParser.java b/bundletool/java/com/android/tools/appbundle/bundletool/utils/FlagParser.java new file mode 100644 index 0000000..5f42165 --- /dev/null +++ b/bundletool/java/com/android/tools/appbundle/bundletool/utils/FlagParser.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool.utils; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Utility for flag parsing, specific to the Bundle Tool. + * + * <p>The flags follow the below convention: + * + * <p>[bundle-tool] [command1] [command2] .. [command-n] [--flag1] [--flag2=v2].. [--flagn] where: + * + * <ul> + * <li>commands: cannot start with "-". + * <li>flags: have to start with "--". If they have "=" anything after the first occurrence is + * considered a flag value. By default the flag value is an empty string. + * </ul> + */ +public class FlagParser { + + private List<String> commands = new ArrayList<>(); + private Map<String, String> flags = new HashMap<>(); + + /** + * Parses the given arguments populating the structures. + * + * <p>Calling this function removes any previous parsing results. + */ + public FlagParser parse(String[] args) throws ParseException { + this.commands.clear(); + // Need to wrap it into a proper list implementation to be able to remove elements. + List<String> argsToProcess = new ArrayList<>(Arrays.asList(args)); + while (argsToProcess.size() > 0 && !argsToProcess.get(0).startsWith("-")) { + commands.add(argsToProcess.get(0)); + argsToProcess.remove(0); + } + this.flags = parseFlags(argsToProcess); + return this; + } + + private Map<String, String> parseFlags(List<String> args) throws ParseException { + Map<String, String> flagMap = new HashMap<>(); + for (String arg : args) { + if (!arg.startsWith("--")) { + throw new ParseException( + String.format("Syntax error: flags should start with -- (%s)", arg)); + } + String[] segments = arg.substring(2).split("=", 2); + String value = ""; + if (segments.length == 2) { + value = segments[1]; + } + if (flagMap.putIfAbsent(segments[0], value) != null) { + throw new ParseException( + String.format("Flag %s has been set more than once.", segments[0])); + } + } + return flagMap; + } + + /** Returns true if a given flag has been set. */ + public boolean isFlagSet(String flagName) { + return flags.containsKey(flagName); + } + + /** Returns the flag value wrapped in the Optional class. */ + public Optional<String> getFlagValue(String flagName) { + return Optional.ofNullable(flags.get(flagName)); + } + + /** + * Returns a flag value. If absent throws IllegalStateException. + * + * @param flagName name of the flag to fetch + * @return string, the value of the flag + * @throws IllegalStateException if the flag was not set. + */ + public String getRequiredFlagValue(String flagName) { + return getFlagValue(flagName) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format("Missing the required --%s flag.", flagName))); + } + + /** Returns the string value of the flag or the default if has not been set. */ + public String getFlagValueOrDefault(String flagName, String defaultValue) { + return flags.getOrDefault(flagName, defaultValue); + } + + public Optional<Path> getFlagValueAsPath(String flagName) { + return Optional.ofNullable(flags.get(flagName)).map(Paths::get); + } + + /** + * Returns the value of the flag as list of strings. + * + * <p>It converts the string flag value to the list assuming it's delimited by a comma. The list + * is empty if the flag has not been set. + */ + public List<String> getFlagListValue(String flagName) { + if (!isFlagSet(flagName)) { + return Collections.emptyList(); + } + return Arrays.asList(flags.get(flagName).split(",")); + } + + /** + * Returns the list of commands that were parsed. + * + * @return the immutable list of commands. + */ + public List<String> getCommands() { + return Collections.unmodifiableList(commands); + } + + /** Exception encapsulating any flag parsing errors. */ + public static class ParseException extends RuntimeException { + + public ParseException(String message) { + super(message); + } + } +} diff --git a/bundletool/javatests/com/android/tools/appbundle/bundletool/AllTests.java b/bundletool/javatests/com/android/tools/appbundle/bundletool/AllTests.java new file mode 100644 index 0000000..acfc60b --- /dev/null +++ b/bundletool/javatests/com/android/tools/appbundle/bundletool/AllTests.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool; + +import org.junit.runner.JUnitCore; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + AppBundleTest.class, + BuildModuleCommandTest.class, + BundleToolTest.class, + SplitModuleCommandTest.class, +}) +public class AllTests { + + public static void main(String[] args) throws Exception { + JUnitCore.main("com.android.tools.appbundle.bundletool.AllTests"); + } +} diff --git a/bundletool/javatests/com/android/tools/appbundle/bundletool/AppBundleTest.java b/bundletool/javatests/com/android/tools/appbundle/bundletool/AppBundleTest.java new file mode 100644 index 0000000..d5a352b --- /dev/null +++ b/bundletool/javatests/com/android/tools/appbundle/bundletool/AppBundleTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for the AppBundle class. */ +@RunWith(JUnit4.class) +public class AppBundleTest { + + private ZipFile bundleFile; + private Vector<ZipEntry> entries = new Vector<>(); + + @Before + public void setUp() { + bundleFile = mock(ZipFile.class); + } + + @Test + public void testSingleModuleBundle() { + putEntry("/module1/classes.dex"); + doReturn(entries.elements()).when(bundleFile).entries(); + + AppBundle appBundle = new AppBundle(bundleFile); + assertThat(appBundle.getModules().keySet()).containsExactly("module1"); + } + + @Test + public void testNoModulesWhenFilesAtRoot() { + putEntry("/deliverables.pb"); + putEntry("variants.pb"); + doReturn(entries.elements()).when(bundleFile).entries(); + + AppBundle appBundle = new AppBundle(bundleFile); + assertThat(appBundle.getModules().keySet()).isEmpty(); + } + + @Test + public void testMultipleModules() { + putEntry("base/AndroidManifest.flat"); + putEntry("base/Format.flat"); + putEntry("base/classes.dex"); + putEntry("base/assets/textures.etc1"); + putEntry("base/res/drawable-hdpi/title.jpg"); + putEntry("detail/AndroidManifest.flat"); + putEntry("detail/Format.flat"); + doReturn(entries.elements()).when(bundleFile).entries(); + + AppBundle appBundle = new AppBundle(bundleFile); + assertThat(appBundle.getModules().keySet()).containsExactly("base", "detail"); + } + + private void putEntry(String fakeFile) { + entries.add(new ZipEntry(fakeFile)); + } +} diff --git a/bundletool/javatests/com/android/tools/appbundle/bundletool/BuildModuleCommandTest.java b/bundletool/javatests/com/android/tools/appbundle/bundletool/BuildModuleCommandTest.java new file mode 100644 index 0000000..7f6b107 --- /dev/null +++ b/bundletool/javatests/com/android/tools/appbundle/bundletool/BuildModuleCommandTest.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.expectThrows; + +import com.android.tools.appbundle.bundletool.utils.FlagParser; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BuildModuleCommandTest { + + @Rule public TemporaryFolder tmp = new TemporaryFolder(); + + private Path outputPath; + private Path manifestPath; + private Path manifestDirPath; + private Path dexPath; + private Path dexDirPath; + private Path resourcesDirPath; + private Path assetsDirPath; + private Path nativeDirPath; + + private Path pathThatDoesNotExist; + + @Before + public void setUp() throws IOException { + outputPath = Paths.get(tmp.getRoot().getPath(), "bundle"); + manifestPath = tmp.newFile("AndroidManifest.flat").toPath(); + manifestDirPath = tmp.newFolder("manifest").toPath(); + dexPath = tmp.newFile("classes.dex").toPath(); + dexDirPath = tmp.newFolder("dex").toPath(); + resourcesDirPath = tmp.newFolder("resources").toPath(); + assetsDirPath = tmp.newFolder("assets").toPath(); + nativeDirPath = tmp.newFolder("native").toPath(); + + pathThatDoesNotExist = Paths.get(tmp.getRoot().getPath(), "path-that-does-not-exist"); + } + + @Test + public void validConfig_viaFlags() { + FlagParser flagParser = + new FlagParser() + .parse( + new String[] { + "--output=" + outputPath, + "--manifest=" + manifestPath, + "--dexPath=" + dexPath, + "--resourcesDirPath=" + resourcesDirPath, + "--assetsDirPath=" + assetsDirPath, + "--nativeDirPath=" + nativeDirPath, + }); + BuildModuleCommand.fromFlags(flagParser).execute(); + } + + @Test + public void validConfig_viaBuilder() { + BuildModuleCommand.builder() + .setOutputPath(outputPath) + .setManifestPath(manifestPath) + .setDexPath(dexPath) + .setResourcesDirPath(resourcesDirPath) + .setAssetsDirPath(assetsDirPath) + .setNativeDirPath(nativeDirPath) + .build() + .execute(); + } + + @Test + public void outputPathNotSetThrowsException_viaFlags() { + FlagParser flagParser = new FlagParser().parse(new String[] {"--manifest=" + manifestPath}); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.fromFlags(flagParser).execute()); + + assertThat(exception).hasMessageThat().contains("--output"); + } + + @Test + public void outputPathNotSetThrowsException_viaBuilder() { + assertThrows( + IllegalStateException.class, + () -> BuildModuleCommand.builder().setManifestPath(manifestPath).build().execute()); + } + + @Test + public void manifestAndManifestDirNotSetThrowsException_viaFlags() { + FlagParser flagParser = new FlagParser().parse(new String[] {"--output=" + outputPath}); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.fromFlags(flagParser).execute()); + + assertThat(exception).hasMessageThat().contains("--manifest"); + } + + @Test + public void manifestAndManifestDirNotSetThrowsException_viaBuilder() { + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.builder().setOutputPath(outputPath).build().execute()); + + assertThat(exception).hasMessageThat().contains("--manifest"); + } + + @Test + public void manifestAndManifestDirBothSetThrowsException_viaFlags() { + FlagParser flagParser = + new FlagParser() + .parse( + new String[] { + "--output=" + outputPath, + "--manifest=" + manifestPath, + "--manifest-dir=" + manifestDirPath + }); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.fromFlags(flagParser).execute()); + + assertThat(exception).hasMessageThat().contains("--manifest"); + } + + @Test + public void manifestAndManifestDirBothSetThrowsException_viaBuilder() { + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> + BuildModuleCommand.builder() + .setOutputPath(outputPath) + .setManifestPath(manifestPath) + .setManifestDirPath(manifestDirPath) + .build() + .execute()); + + assertThat(exception).hasMessageThat().contains("--manifest"); + } + + @Test + public void dexAndDexDirBothSetThrowsException_viaFlags() { + FlagParser flagParser = + new FlagParser() + .parse( + new String[] { + "--output=" + outputPath, + "--manifest=" + manifestPath, + "--dex=" + dexPath, + "--dex-dir=" + dexDirPath + }); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.fromFlags(flagParser).execute()); + + assertThat(exception).hasMessageThat().contains("--dex"); + } + + @Test + public void dexAndDexDirBothSetThrowsException_viaBuilder() { + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> + BuildModuleCommand.builder() + .setOutputPath(outputPath) + .setManifestPath(manifestPath) + .setDexPath(dexPath) + .setDexDirPath(dexDirPath) + .build() + .execute()); + + assertThat(exception).hasMessageThat().contains("--dex"); + } + + @Test + public void outputExistsThrowsException_viaFlags() throws IOException { + outputPath = tmp.newFile("bundle").toPath(); + FlagParser flagParser = + new FlagParser() + .parse( + new String[] { + "--output=" + outputPath, "--manifest=" + manifestPath, "--dex=" + dexPath + }); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.fromFlags(flagParser).execute()); + + assertThat(exception).hasMessageThat().contains("already exists"); + } + + @Test + public void outputExistsThrowsException_viaBuilder() throws IOException { + outputPath = tmp.newFile("bundle").toPath(); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> + BuildModuleCommand.builder() + .setOutputPath(outputPath) + .setManifestPath(manifestPath) + .setDexPath(dexPath) + .build() + .execute()); + + assertThat(exception).hasMessageThat().contains("already exists"); + } + + @Test + public void manifestDoesNotExistThrowsException_viaFlags() throws IOException { + FlagParser flagParser = + new FlagParser() + .parse( + new String[] { + "--output=" + outputPath, "--manifest=" + pathThatDoesNotExist, "--dex=" + dexPath + }); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.fromFlags(flagParser).execute()); + + assertThat(exception).hasMessageThat().containsMatch("File '.*' was not found"); + } + + @Test + public void manifestDoesNotExistThrowsException_viaBuilder() throws IOException { + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> + BuildModuleCommand.builder() + .setOutputPath(outputPath) + .setManifestPath(pathThatDoesNotExist) + .setDexPath(dexPath) + .build() + .execute()); + + assertThat(exception).hasMessageThat().containsMatch("File '.*' was not found"); + } + + @Test + public void manifestDirDoesNotExistThrowsException_viaFlags() throws IOException { + FlagParser flagParser = + new FlagParser() + .parse( + new String[] { + "--output=" + outputPath, + "--manifest-dir=" + pathThatDoesNotExist, + "--dex=" + dexPath + }); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.fromFlags(flagParser).execute()); + + assertThat(exception).hasMessageThat().containsMatch("Directory '.*' was not found"); + } + + @Test + public void manifestDirDoesNotExistThrowsException_viaBuilder() throws IOException { + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> + BuildModuleCommand.builder() + .setOutputPath(outputPath) + .setManifestDirPath(pathThatDoesNotExist) + .setDexPath(dexPath) + .build() + .execute()); + + assertThat(exception).hasMessageThat().containsMatch("Directory '.*' was not found"); + } + + @Test + public void dexDoesNotExistThrowsException_viaFlags() throws IOException { + FlagParser flagParser = + new FlagParser() + .parse( + new String[] { + "--output=" + outputPath, + "--manifest=" + manifestPath, + "--dex=" + pathThatDoesNotExist + }); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.fromFlags(flagParser).execute()); + + assertThat(exception).hasMessageThat().containsMatch("File '.*' was not found"); + } + + @Test + public void dexDoesNotExistThrowsException_viaBuilder() throws IOException { + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> + BuildModuleCommand.builder() + .setOutputPath(outputPath) + .setManifestPath(manifestPath) + .setDexPath(pathThatDoesNotExist) + .build() + .execute()); + + assertThat(exception).hasMessageThat().containsMatch("File '.*' was not found"); + } + + @Test + public void dexDirDoesNotExistThrowsException_viaFlags() throws IOException { + dexDirPath = Paths.get(tmp.getRoot().getPath(), "dir-that-does-not-exist"); + FlagParser flagParser = + new FlagParser() + .parse( + new String[] { + "--output=" + outputPath, + "--manifest=" + manifestPath, + "--dex-dir=" + pathThatDoesNotExist + }); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.fromFlags(flagParser).execute()); + + assertThat(exception).hasMessageThat().containsMatch("Directory '.*' was not found"); + } + + @Test + public void dexDirDoesNotExistThrowsException_viaBuilder() throws IOException { + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> + BuildModuleCommand.builder() + .setOutputPath(outputPath) + .setManifestPath(manifestPath) + .setDexDirPath(pathThatDoesNotExist) + .build() + .execute()); + + assertThat(exception).hasMessageThat().containsMatch("Directory '.*' was not found"); + } + + @Test + public void resourcesDirDoesNotExistThrowsException_viaFlags() throws IOException { + FlagParser flagParser = + new FlagParser() + .parse( + new String[] { + "--output=" + outputPath, + "--manifest=" + manifestPath, + "--resources-dir=" + pathThatDoesNotExist + }); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.fromFlags(flagParser).execute()); + + assertThat(exception).hasMessageThat().containsMatch("Directory '.*' was not found"); + } + + @Test + public void resourcesDirDoesNotExistThrowsException_viaBuilder() throws IOException { + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> + BuildModuleCommand.builder() + .setOutputPath(outputPath) + .setManifestPath(manifestPath) + .setResourcesDirPath(pathThatDoesNotExist) + .build() + .execute()); + + assertThat(exception).hasMessageThat().containsMatch("Directory '.*' was not found"); + } + + @Test + public void assetsDirDoesNotExistThrowsException_viaFlags() throws IOException { + FlagParser flagParser = + new FlagParser() + .parse( + new String[] { + "--output=" + outputPath, + "--manifest=" + manifestPath, + "--assets-dir=" + pathThatDoesNotExist + }); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.fromFlags(flagParser).execute()); + + assertThat(exception).hasMessageThat().containsMatch("Directory '.*' was not found"); + } + + @Test + public void assetsDirDoesNotExistThrowsException_viaBuilder() throws IOException { + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> + BuildModuleCommand.builder() + .setOutputPath(outputPath) + .setManifestPath(manifestPath) + .setAssetsDirPath(pathThatDoesNotExist) + .build() + .execute()); + + assertThat(exception).hasMessageThat().containsMatch("Directory '.*' was not found"); + } + + @Test + public void nativeDirDoesNotExistThrowsException_viaFlags() throws IOException { + FlagParser flagParser = + new FlagParser() + .parse( + new String[] { + "--output=" + outputPath, + "--manifest=" + manifestPath, + "--native-dir=" + pathThatDoesNotExist + }); + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> BuildModuleCommand.fromFlags(flagParser).execute()); + + assertThat(exception).hasMessageThat().containsMatch("Directory '.*' was not found"); + } + + @Test + public void nativeDirDoesNotExistThrowsException_viaBuilder() throws IOException { + IllegalArgumentException exception = + expectThrows( + IllegalArgumentException.class, + () -> + BuildModuleCommand.builder() + .setOutputPath(outputPath) + .setManifestPath(manifestPath) + .setNativeDirPath(pathThatDoesNotExist) + .build() + .execute()); + + assertThat(exception).hasMessageThat().containsMatch("Directory '.*' was not found"); + } +} diff --git a/bundletool/javatests/com/android/tools/appbundle/bundletool/BundleToolTest.java b/bundletool/javatests/com/android/tools/appbundle/bundletool/BundleToolTest.java new file mode 100644 index 0000000..2c69b01 --- /dev/null +++ b/bundletool/javatests/com/android/tools/appbundle/bundletool/BundleToolTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for Bundle tool. + */ +@RunWith(JUnit4.class) +public final class BundleToolTest { + + @Test + public void dummyTest() { + } +} diff --git a/bundletool/javatests/com/android/tools/appbundle/bundletool/SplitModuleCommandTest.java b/bundletool/javatests/com/android/tools/appbundle/bundletool/SplitModuleCommandTest.java new file mode 100644 index 0000000..0006cb0 --- /dev/null +++ b/bundletool/javatests/com/android/tools/appbundle/bundletool/SplitModuleCommandTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool; + +import static org.junit.Assert.assertThrows; + +import com.android.tools.appbundle.bundletool.Command.ExecutionException; +import com.android.tools.appbundle.bundletool.utils.FlagParser; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for the SplitModuleCommand implementation. */ +@RunWith(JUnit4.class) +public class SplitModuleCommandTest { + + @Test + public void testMissingOutputFlag() throws Exception { + expectFlagException(new String[] {"--bundle=b.zip", "--module=m1"}); + } + + @Test + public void testMissingBundleFlag() throws Exception { + expectFlagException(new String[] {"--output=/some/dir", "--module=m1"}); + } + + @Test + public void testMissingModuleFlag() throws Exception { + expectFlagException(new String[] {"--output=/some/dir", "--bundle=b.zip"}); + } + + @Test + public void testMissingBundleFileFailsCommand() throws Exception { + FlagParser flagParser = new FlagParser(); + flagParser.parse(new String[] {"--bundle=b.zip", "--output=/some/dir", "--module=m1"}); + SplitModuleCommand command = new SplitModuleCommand(flagParser); + assertThrows(ExecutionException.class, () -> command.execute()); + } + + private void expectFlagException(String[] flags) throws Exception { + FlagParser flagParser = new FlagParser(); + flagParser.parse(flags); + assertThrows(IllegalArgumentException.class, () -> new SplitModuleCommand(flagParser)); + } +} diff --git a/bundletool/javatests/com/android/tools/appbundle/bundletool/utils/FlagParserTest.java b/bundletool/javatests/com/android/tools/appbundle/bundletool/utils/FlagParserTest.java new file mode 100644 index 0000000..f9fc6d5 --- /dev/null +++ b/bundletool/javatests/com/android/tools/appbundle/bundletool/utils/FlagParserTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2017 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.tools.appbundle.bundletool.utils; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.fail; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for the FlagParser class. */ +@RunWith(JUnit4.class) +public class FlagParserTest { + + @Test + public void testParsesSingleCommand() throws Exception { + String[] args = {"command1"}; + FlagParser fp = new FlagParser(); + fp.parse(args); + assertThat(fp.getCommands()).containsExactly("command1"); + } + + @Test + public void testEmptyCommandLine() throws Exception { + String[] args = {}; + FlagParser fp = new FlagParser(); + fp.parse(args); + assertThat(fp.getCommands()).isEmpty(); + } + + @Test + public void testParsesCommandLineFlags() throws Exception { + String[] args = {"command", "--flag1=value1", "--flag2=value2"}; + FlagParser fp = new FlagParser(); + fp.parse(args); + assertThat(fp.getCommands()).containsExactly("command"); + assertThat(fp.getFlagValueOrDefault("flag1", "")).isEqualTo("value1"); + assertThat(fp.getFlagValueOrDefault("flag2", "")).isEqualTo("value2"); + } + + @Test + public void testIncorrectCommandLineFlagsThrows() throws Exception { + String[] args = {"command", "--flag1=value1", "-flag2=value2"}; + FlagParser fp = new FlagParser(); + try { + fp.parse(args); + fail("Expected ParseException but nothing was thrown."); + } catch (FlagParser.ParseException e) { + assertThat(e.getMessage()).contains("-flag2=value2"); + } + } + + @Test + public void testFlagListValuesNotSet() throws Exception { + String[] args = {"command"}; + FlagParser fp = new FlagParser(); + fp.parse(args); + assertThat(fp.getCommands()).containsExactly("command"); + assertThat(fp.getFlagListValue("flag1")).isEmpty(); + } + + @Test + public void testFlagListValuesSetWithDefault() throws Exception { + String[] args = {"command", "--flag1"}; + FlagParser fp = new FlagParser(); + fp.parse(args); + assertThat(fp.getCommands()).containsExactly("command"); + assertThat(fp.getFlagListValue("flag1")).containsExactly(""); + } + + @Test + public void testFlagListValuesSingleValue() throws Exception { + String[] args = {"command", "--flag1=val1"}; + FlagParser fp = new FlagParser(); + fp.parse(args); + assertThat(fp.getFlagListValue("flag1")).containsExactly("val1"); + } + + @Test + public void testFlagListValuesMultiple() throws Exception { + String[] args = {"command", "--flag1=v1,v2,value3"}; + FlagParser fp = new FlagParser(); + fp.parse(args); + assertThat(fp.getCommands()).containsExactly("command"); + assertThat(fp.getFlagValueOrDefault("flag1", "")).isEqualTo("v1,v2,value3"); + assertThat(fp.getFlagListValue("flag1")).containsExactly("v1", "v2", "value3").inOrder(); + } + + @Test + public void testHandlingAbsentFlags() throws Exception { + String[] args = {"command", "--flag1=v1"}; + FlagParser fp = new FlagParser(); + fp.parse(args); + assertThat(fp.getCommands()).containsExactly("command"); + assertThat(fp.getFlagValueOrDefault("flag2", "default")).isEqualTo("default"); + assertThat(fp.getFlagListValue("flag2")).isEmpty(); + assertThat(fp.isFlagSet("flag2")).isFalse(); + } + + @Test + public void testHandlingMultipleCommands() throws Exception { + String[] args = {"help", "command1"}; + FlagParser fp = new FlagParser(); + fp.parse(args); + assertThat(fp.getCommands()).containsExactly("help", "command1").inOrder(); + } + + @Test + public void testUsingFlagMoreThanOnceThrows() throws Exception { + String[] args = {"command", "--flag1=v1", "--flag1=v2"}; + FlagParser fp = new FlagParser(); + try { + fp.parse(args); + fail("Expected ParseException but nothing was thrown."); + } catch (FlagParser.ParseException e) { + assertThat(e.getMessage()).contains("flag1"); + } + } +} |