From 841c3e3ea606824a134dbeb32ccb4e2ff2fcf1ed Mon Sep 17 00:00:00 2001 From: Joe Onorato Date: Mon, 19 Oct 2020 23:28:59 -0700 Subject: [PATCH] Add skeleton for product-config tool. There's also a bunch of boilerplate error handling and option parsing stuff that I wish someone would put into a library that's available to the android tree. Test: atest product-config-test Change-Id: Ieebcc7bd47a8644d1374fb02c146e9038859f4a2 --- tools/product_config/Android.bp | 23 ++ tools/product_config/MANIFEST.MF | 2 + tools/product_config/TEST_MAPPING | 7 + .../android/build/config/ErrorReporter.java | 263 ++++++++++++++++++ .../src/com/android/build/config/Errors.java | 45 +++ .../src/com/android/build/config/Main.java | 64 +++++ .../src/com/android/build/config/Options.java | 158 +++++++++++ .../com/android/build/config/Position.java | 78 ++++++ .../build/config/ErrorReporterTest.java | 121 ++++++++ .../com/android/build/config/OptionsTest.java | 114 ++++++++ .../com/android/build/config/TestErrors.java | 80 ++++++ 11 files changed, 955 insertions(+) create mode 100644 tools/product_config/Android.bp create mode 100644 tools/product_config/MANIFEST.MF create mode 100644 tools/product_config/TEST_MAPPING create mode 100644 tools/product_config/src/com/android/build/config/ErrorReporter.java create mode 100644 tools/product_config/src/com/android/build/config/Errors.java create mode 100644 tools/product_config/src/com/android/build/config/Main.java create mode 100644 tools/product_config/src/com/android/build/config/Options.java create mode 100644 tools/product_config/src/com/android/build/config/Position.java create mode 100644 tools/product_config/test/com/android/build/config/ErrorReporterTest.java create mode 100644 tools/product_config/test/com/android/build/config/OptionsTest.java create mode 100644 tools/product_config/test/com/android/build/config/TestErrors.java diff --git a/tools/product_config/Android.bp b/tools/product_config/Android.bp new file mode 100644 index 000000000..287ed5ac4 --- /dev/null +++ b/tools/product_config/Android.bp @@ -0,0 +1,23 @@ +java_defaults { + name: "product-config-defaults", + srcs: ["src/**/*.java"], +} + +java_binary_host { + name: "product-config", + defaults: ["product-config-defaults"], + manifest: "MANIFEST.MF" +} + +java_test_host { + name: "product-config-test", + defaults: ["product-config-defaults"], + srcs: [ + "test/**/*.java", + ], + static_libs: [ + "junit" + ], + test_suites: ["general-tests"] +} + diff --git a/tools/product_config/MANIFEST.MF b/tools/product_config/MANIFEST.MF new file mode 100644 index 000000000..db88df34f --- /dev/null +++ b/tools/product_config/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 +Main-Class: com.android.build.config.Main diff --git a/tools/product_config/TEST_MAPPING b/tools/product_config/TEST_MAPPING new file mode 100644 index 000000000..d3568f134 --- /dev/null +++ b/tools/product_config/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "product_config_test" + } + ] +} diff --git a/tools/product_config/src/com/android/build/config/ErrorReporter.java b/tools/product_config/src/com/android/build/config/ErrorReporter.java new file mode 100644 index 000000000..f382b4e1b --- /dev/null +++ b/tools/product_config/src/com/android/build/config/ErrorReporter.java @@ -0,0 +1,263 @@ +/* + * 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.build.config; + +import java.lang.reflect.Field; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Base class for reporting errors. + */ +public class ErrorReporter { + /** + * List of Entries that have occurred. + */ + // Also used as the lock for this object. + private final ArrayList mEntries = new ArrayList(); + + /** + * The categories that are for this Errors object. + */ + private Map mCategories; + + /** + * Whether there has been a warning or an error yet. + */ + private boolean mHadWarningOrError; + + /** + * Whether there has been an error yet. + */ + private boolean mHadError; + + /** + * Whether errors are errors, warnings or hidden. + */ + public static enum Level { + HIDDEN("hidden"), + WARNING("warning"), + ERROR("error"); + + private final String mLabel; + + Level(String label) { + mLabel = label; + } + + String getLabel() { + return mLabel; + } + } + + /** + * The available error codes. + */ + public class Category { + private final int mCode; + private boolean mIsLevelSettable; + private Level mLevel; + private String mHelp; + + /** + * Construct a Category object. + */ + public Category(int code, boolean isLevelSettable, Level level, String help) { + if (!isLevelSettable && level != Level.ERROR) { + throw new RuntimeException("Don't have WARNING or HIDDEN without isLevelSettable"); + } + mCode = code; + mIsLevelSettable = isLevelSettable; + mLevel = level; + mHelp = help; + } + + /** + * Get the numeric code for the Category, which can be used to set the level. + */ + public int getCode() { + return mCode; + } + + /** + * Get whether the level of this Category can be changed. + */ + public boolean isLevelSettable() { + return mIsLevelSettable; + } + + /** + * Set the level of this category. + */ + public void setLevel(Level level) { + if (!mIsLevelSettable) { + throw new RuntimeException("Can't set level for error " + mCode); + } + mLevel = level; + } + + /** + * Return the level, including any overrides. + */ + public Level getLevel() { + return mLevel; + } + + /** + * Return the category's help text. + */ + public String getHelp() { + return mHelp; + } + } + + /** + * An instance of an error happening. + */ + public class Entry { + private final Category mCategory; + private final Position mPosition; + private final String mMessage; + + Entry(Category category, Position position, String message) { + mCategory = category; + mPosition = position; + mMessage = message; + } + + public Category getCategory() { + return mCategory; + } + + public Position getPosition() { + return mPosition; + } + + public String getMessage() { + return mMessage; + } + } + + private void initLocked() { + if (mCategories == null) { + HashMap categories = new HashMap(); + for (Field field: getClass().getFields()) { + if (Category.class.isAssignableFrom(field.getType())) { + Category category = null; + try { + category = (Category)field.get(this); + } catch (IllegalAccessException ex) { + // Wrap and rethrow, this is always on this class, so it's + // our programming error if this happens. + throw new RuntimeException("Categories on Errors should be public.", ex); + } + Category prev = categories.put(category.getCode(), category); + if (prev != null) { + throw new RuntimeException("Duplicate categories with code " + + category.getCode()); + } + } + } + mCategories = Collections.unmodifiableMap(categories); + } + } + + /** + * Returns a map of the category codes to the categories. + */ + public Map getCategories() { + synchronized (mEntries) { + initLocked(); + return mCategories; + } + } + + /** + * Add an error with no source position. + */ + public void add(Category category, String message) { + add(category, new Position(), message); + } + + /** + * Add an error. + */ + public void add(Category category, Position pos, String message) { + synchronized (mEntries) { + initLocked(); + if (mCategories.get(category.getCode()) != category) { + throw new RuntimeException("Errors.Category used from the wrong Errors object."); + } + mEntries.add(new Entry(category, pos, message)); + final Level level = category.getLevel(); + if (level == Level.WARNING || level == Level.ERROR) { + mHadWarningOrError = true; + } + if (level == Level.ERROR) { + mHadError = true; + } + } + } + + /** + * Returns whether there has been a warning or an error yet. + */ + public boolean hadWarningOrError() { + synchronized (mEntries) { + return mHadWarningOrError; + } + } + + /** + * Returns whether there has been an error yet. + */ + public boolean hadError() { + synchronized (mEntries) { + return mHadError; + } + } + + /** + * Returns a list of all entries that were added. + */ + public List getEntries() { + synchronized (mEntries) { + return new ArrayList(mEntries); + } + } + + /** + * Prints the errors. + */ + public void printErrors(PrintStream out) { + synchronized (mEntries) { + for (Entry entry: mEntries) { + final Category category = entry.getCategory(); + final Level level = category.getLevel(); + if (level == Level.HIDDEN) { + continue; + } + out.println(entry.getPosition() + "[" + level.getLabel() + " " + + category.getCode() + "] " + entry.getMessage()); + } + } + } +} diff --git a/tools/product_config/src/com/android/build/config/Errors.java b/tools/product_config/src/com/android/build/config/Errors.java new file mode 100644 index 000000000..63792c8c8 --- /dev/null +++ b/tools/product_config/src/com/android/build/config/Errors.java @@ -0,0 +1,45 @@ +/* + * 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.build.config; + +import java.lang.reflect.Field; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Error constants and error reporting. + *

+ * Naming Convention: + *

    + *
  • ERROR_ for Categories with isLevelSettable false and Level.ERROR + *
  • WARNING_ for Categories with isLevelSettable false and default WARNING or HIDDEN + *
  • Don't have isLevelSettable true and not ERROR. (The constructor asserts this). + *
+ */ +public class Errors extends ErrorReporter { + + public final Category ERROR_COMMAND_LINE = new Category(1, false, Level.ERROR, + "Error on the command line."); + + public final Category WARNING_UNKNOWN_COMMAND_LINE_ERROR = new Category(2, true, Level.HIDDEN, + "Passing unknown errors on the command line. Hidden by default for\n" + + "forward compatibility."); +} diff --git a/tools/product_config/src/com/android/build/config/Main.java b/tools/product_config/src/com/android/build/config/Main.java new file mode 100644 index 000000000..766974293 --- /dev/null +++ b/tools/product_config/src/com/android/build/config/Main.java @@ -0,0 +1,64 @@ +/* + * 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.build.config; + +public class Main { + private final Errors mErrors; + private final Options mOptions; + + public Main(Errors errors, Options options) { + mErrors = errors; + mOptions = options; + } + + void run() { + System.out.println("Hello World"); + + // TODO: Check the build environment to make sure we're running in a real + // build environment, e.g. actually inside a source tree, with TARGET_PRODUCT + // and TARGET_BUILD_VARIANT defined, etc. + + // TODO: Run kati and extract the variables and convert all that into starlark files. + + // TODO: Run starlark with all the generated ones and the hand written ones. + + // TODO: Get the variables that were defined in starlark and use that to write + // out the make, soong and bazel input files. + } + + public static void main(String[] args) { + Errors errors = new Errors(); + + Options options = Options.parse(errors, args); + if (errors.hadError()) { + Options.printHelp(System.err); + System.err.println(); + errors.printErrors(System.err); + System.exit(1); + } + + switch (options.getAction()) { + case DEFAULT: + (new Main(errors, options)).run(); + errors.printErrors(System.err); + return; + case HELP: + Options.printHelp(System.out); + return; + } + } +} diff --git a/tools/product_config/src/com/android/build/config/Options.java b/tools/product_config/src/com/android/build/config/Options.java new file mode 100644 index 000000000..494b947f3 --- /dev/null +++ b/tools/product_config/src/com/android/build/config/Options.java @@ -0,0 +1,158 @@ +/* + * 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.build.config; + +import java.io.PrintStream; +import java.util.TreeMap; + +public class Options { + public enum Action { + DEFAULT, + HELP + } + + private Action mAction = Action.DEFAULT; + + public Action getAction() { + return mAction; + } + + public static void printHelp(PrintStream out) { + out.println("usage: product_config"); + out.println(); + out.println("OPTIONS"); + out.println(" --hide ERROR_ID Suppress this error."); + out.println(" --error ERROR_ID Make this ERROR_ID a fatal error."); + out.println(" --help -h This message."); + out.println(" --warning ERROR_ID Make this ERROR_ID a warning."); + out.println(); + out.println("ERRORS"); + out.println(" The following are the errors that can be controlled on the"); + out.println(" commandline with the --hide --warning --error flags."); + + TreeMap sorted = new TreeMap((new Errors()).getCategories()); + + for (final Errors.Category category: sorted.values()) { + if (category.isLevelSettable()) { + out.println(String.format(" %-3d %s", category.getCode(), + category.getHelp().replace("\n", "\n "))); + } + } + } + + static class Parser { + private class ParseException extends Exception { + public ParseException(String message) { + super(message); + } + } + + private Errors mErrors; + private String[] mArgs; + private Options mResult = new Options(); + private int mIndex; + + public Parser(Errors errors, String[] args) { + mErrors = errors; + mArgs = args; + } + + public Options parse() { + try { + while (mIndex < mArgs.length) { + final String arg = mArgs[mIndex]; + + if ("--hide".equals(arg)) { + handleErrorCode(arg, Errors.Level.HIDDEN); + } else if ("--error".equals(arg)) { + handleErrorCode(arg, Errors.Level.ERROR); + } else if ("--help".equals(arg) || "-h".equals(arg)) { + // Help overrides all other commands if there isn't an error, but + // we will stop here. + if (!mErrors.hadError()) { + mResult.mAction = Action.HELP; + } + return mResult; + } else if ("--warning".equals(arg)) { + handleErrorCode(arg, Errors.Level.WARNING); + } else { + throw new ParseException("Unknown command line argument: " + arg); + } + + mIndex++; + } + } catch (ParseException ex) { + mErrors.add(mErrors.ERROR_COMMAND_LINE, ex.getMessage()); + } + + return mResult; + } + + private void addWarning(Errors.Category category, String message) { + mErrors.add(category, message); + } + + private String getNextNonFlagArg() { + if (mIndex == mArgs.length - 1) { + return null; + } + if (mArgs[mIndex + 1].startsWith("-")) { + return null; + } + mIndex++; + return mArgs[mIndex]; + } + + private int requireNextNumberArg(String arg) throws ParseException { + final String val = getNextNonFlagArg(); + if (val == null) { + throw new ParseException(arg + " requires a numeric argument."); + } + try { + return Integer.parseInt(val); + } catch (NumberFormatException ex) { + throw new ParseException(arg + " requires a numeric argument. found: " + val); + } + } + + private void handleErrorCode(String arg, Errors.Level level) throws ParseException { + final int code = requireNextNumberArg(arg); + final Errors.Category category = mErrors.getCategories().get(code); + if (category == null) { + mErrors.add(mErrors.WARNING_UNKNOWN_COMMAND_LINE_ERROR, + "Unknown error code: " + code); + return; + } + if (!category.isLevelSettable()) { + mErrors.add(mErrors.ERROR_COMMAND_LINE, "Can't set level for error " + code); + return; + } + category.setLevel(level); + } + } + + /** + * Parse the arguments and return an options object. + *

+ * Updates errors with the hidden / warning / error levels. + *

+ * Adds errors encountered to Errors object. + */ + public static Options parse(Errors errors, String[] args) { + return (new Parser(errors, args)).parse(); + } +} diff --git a/tools/product_config/src/com/android/build/config/Position.java b/tools/product_config/src/com/android/build/config/Position.java new file mode 100644 index 000000000..795394271 --- /dev/null +++ b/tools/product_config/src/com/android/build/config/Position.java @@ -0,0 +1,78 @@ +/* + * 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.build.config; + +/** + * Position in a source file. + */ +public class Position implements Comparable { + /** + * Sentinel line number for when there is no known line number. + */ + public static final int NO_LINE = -1; + + private final String mFile; + private final int mLine; + + public Position() { + mFile = null; + mLine = NO_LINE; + } + + public Position(String file) { + mFile = file; + mLine = NO_LINE; + } + + public Position(String file, int line) { + if (line < NO_LINE) { + throw new IllegalArgumentException("Negative line number. file=" + file + + " line=" + line); + } + mFile = file; + mLine = line; + } + + public int compareTo(Position that) { + int result = mFile.compareTo(that.mFile); + if (result != 0) { + return result; + } + return mLine - that.mLine; + } + + public String getFile() { + return mFile; + } + + public int getLine() { + return mLine; + } + + @Override + public String toString() { + if (mFile == null && mLine == NO_LINE) { + return ""; + } else if (mFile == null && mLine != NO_LINE) { + return ":" + mLine + ": "; + } else if (mFile != null && mLine == NO_LINE) { + return mFile + ": "; + } else { // if (mFile != null && mLine != NO_LINE) + return mFile + ':' + mLine + ": "; + } + } +} diff --git a/tools/product_config/test/com/android/build/config/ErrorReporterTest.java b/tools/product_config/test/com/android/build/config/ErrorReporterTest.java new file mode 100644 index 000000000..2cde4769a --- /dev/null +++ b/tools/product_config/test/com/android/build/config/ErrorReporterTest.java @@ -0,0 +1,121 @@ +/* + * 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.build.config; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashSet; +import java.util.List; + +public class ErrorReporterTest { + /** + * Test that errors can be recorded and retrieved. + */ + @Test + public void testAdding() { + TestErrors errors = new TestErrors(); + + errors.add(errors.ERROR, new Position("a", 12), "Errrororrrr"); + + Assert.assertTrue(errors.hadWarningOrError()); + Assert.assertTrue(errors.hadError()); + + List entries = errors.getEntries(); + Assert.assertEquals(1, entries.size()); + + TestErrors.Entry entry = entries.get(0); + Assert.assertEquals(errors.ERROR, entry.getCategory()); + Assert.assertEquals("a", entry.getPosition().getFile()); + Assert.assertEquals(12, entry.getPosition().getLine()); + Assert.assertEquals("Errrororrrr", entry.getMessage()); + + Assert.assertNotEquals("", errors.getErrorMessages()); + } + + /** + * Test that not adding an error doesn't record errors. + */ + @Test + public void testNoError() { + TestErrors errors = new TestErrors(); + + Assert.assertFalse(errors.hadWarningOrError()); + Assert.assertFalse(errors.hadError()); + Assert.assertEquals("", errors.getErrorMessages()); + } + + /** + * Test that not adding a warning doesn't record errors. + */ + @Test + public void testWarning() { + TestErrors errors = new TestErrors(); + + errors.add(errors.WARNING, "Waaaaarninggggg"); + + Assert.assertTrue(errors.hadWarningOrError()); + Assert.assertFalse(errors.hadError()); + Assert.assertNotEquals("", errors.getErrorMessages()); + } + + /** + * Test that hidden warnings don't report. + */ + @Test + public void testHidden() { + TestErrors errors = new TestErrors(); + + errors.add(errors.HIDDEN, "Hidddeennn"); + + Assert.assertFalse(errors.hadWarningOrError()); + Assert.assertFalse(errors.hadError()); + Assert.assertEquals("", errors.getErrorMessages()); + } + + /** + * Test changing an error level. + */ + @Test + public void testSetLevel() { + TestErrors errors = new TestErrors(); + Assert.assertEquals(TestErrors.Level.ERROR, errors.ERROR.getLevel()); + + errors.ERROR.setLevel(TestErrors.Level.WARNING); + + Assert.assertEquals(TestErrors.Level.WARNING, errors.ERROR.getLevel()); + } + + /** + * Test that changing a fixed error fails. + */ + @Test + public void testSetLevelFails() { + TestErrors errors = new TestErrors(); + Assert.assertEquals(TestErrors.Level.ERROR, errors.ERROR_FIXED.getLevel()); + + boolean exceptionThrown = false; + try { + errors.ERROR_FIXED.setLevel(TestErrors.Level.WARNING); + } catch (RuntimeException ex) { + exceptionThrown = true; + } + + Assert.assertTrue(exceptionThrown); + Assert.assertEquals(TestErrors.Level.ERROR, errors.ERROR_FIXED.getLevel()); + } +} diff --git a/tools/product_config/test/com/android/build/config/OptionsTest.java b/tools/product_config/test/com/android/build/config/OptionsTest.java new file mode 100644 index 000000000..2c3632258 --- /dev/null +++ b/tools/product_config/test/com/android/build/config/OptionsTest.java @@ -0,0 +1,114 @@ +/* + * 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.build.config; + +import org.junit.Assert; +import org.junit.Test; + +public class OptionsTest { + @Test + public void testErrorMissingLast() { + final Errors errors = new Errors(); + + final Options options = Options.parse(errors, new String[] { + "--error" + }); + + Assert.assertNotEquals("", TestErrors.getErrorMessages(errors)); + Assert.assertEquals(Options.Action.DEFAULT, options.getAction()); + TestErrors.assertHasEntry(errors.ERROR_COMMAND_LINE, errors); + } + + @Test + public void testErrorMissingNotLast() { + final Errors errors = new Errors(); + + final Options options = Options.parse(errors, new String[] { + "--error", "--warning", "2" + }); + + Assert.assertNotEquals("", TestErrors.getErrorMessages(errors)); + Assert.assertEquals(Options.Action.DEFAULT, options.getAction()); + TestErrors.assertHasEntry(errors.ERROR_COMMAND_LINE, errors); + } + + @Test + public void testErrorNotNumeric() { + final Errors errors = new Errors(); + + final Options options = Options.parse(errors, new String[] { + "--error", "notgood" + }); + + Assert.assertNotEquals("", TestErrors.getErrorMessages(errors)); + Assert.assertEquals(Options.Action.DEFAULT, options.getAction()); + TestErrors.assertHasEntry(errors.ERROR_COMMAND_LINE, errors); + } + + @Test + public void testErrorInvalidError() { + final Errors errors = new Errors(); + + final Options options = Options.parse(errors, new String[] { + "--error", "50000" + }); + + Assert.assertEquals("", TestErrors.getErrorMessages(errors)); + Assert.assertEquals(Options.Action.DEFAULT, options.getAction()); + TestErrors.assertHasEntry(errors.WARNING_UNKNOWN_COMMAND_LINE_ERROR, errors); + } + + @Test + public void testErrorOne() { + final Errors errors = new Errors(); + + final Options options = Options.parse(errors, new String[] { + "--error", "2" + }); + + Assert.assertEquals("", TestErrors.getErrorMessages(errors)); + Assert.assertEquals(Options.Action.DEFAULT, options.getAction()); + Assert.assertFalse(errors.hadWarningOrError()); + } + + @Test + public void testWarningOne() { + final Errors errors = new Errors(); + + final Options options = Options.parse(errors, new String[] { + "--warning", "2" + }); + + Assert.assertEquals("", TestErrors.getErrorMessages(errors)); + Assert.assertEquals(Options.Action.DEFAULT, options.getAction()); + Assert.assertFalse(errors.hadWarningOrError()); + } + + @Test + public void testHideOne() { + final Errors errors = new Errors(); + + final Options options = Options.parse(errors, new String[] { + "--hide", "2" + }); + + Assert.assertEquals("", TestErrors.getErrorMessages(errors)); + Assert.assertEquals(Options.Action.DEFAULT, options.getAction()); + Assert.assertFalse(errors.hadWarningOrError()); + } +} + diff --git a/tools/product_config/test/com/android/build/config/TestErrors.java b/tools/product_config/test/com/android/build/config/TestErrors.java new file mode 100644 index 000000000..dde88b0da --- /dev/null +++ b/tools/product_config/test/com/android/build/config/TestErrors.java @@ -0,0 +1,80 @@ +/* + * 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.build.config; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + +/** + * Errors for testing. + */ +public class TestErrors extends ErrorReporter { + + public static final int ERROR_CODE = 1; + + public final Category ERROR = new Category(ERROR_CODE, true, Level.ERROR, + "An error."); + + public static final int WARNING_CODE = 2; + + public final Category WARNING = new Category(WARNING_CODE, true, Level.WARNING, + "A warning."); + + public static final int HIDDEN_CODE = 3; + + public final Category HIDDEN = new Category(HIDDEN_CODE, true, Level.HIDDEN, + "A hidden warning."); + + public static final int ERROR_FIXED_CODE = 4; + + public final Category ERROR_FIXED = new Category(ERROR_FIXED_CODE, false, Level.ERROR, + "An error that can't have its level changed."); + + public void assertHasEntry(Errors.Category category) { + assertHasEntry(category, this); + } + + public String getErrorMessages() { + return getErrorMessages(this); + } + + public static void assertHasEntry(Errors.Category category, ErrorReporter errors) { + StringBuilder found = new StringBuilder(); + for (Errors.Entry entry: errors.getEntries()) { + if (entry.getCategory() == category) { + return; + } + found.append(' '); + found.append(entry.getCategory().getCode()); + } + throw new AssertionError("No error category " + category.getCode() + " found." + + " Found category codes were:" + found); + } + + public static String getErrorMessages(ErrorReporter errors) { + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + errors.printErrors(new PrintStream(stream, true, StandardCharsets.UTF_8.name())); + } catch (UnsupportedEncodingException ex) { + // utf-8 is always supported + } + return new String(stream.toByteArray(), StandardCharsets.UTF_8); + } +} +