diff options
author | Yigit Boyar <yboyar@google.com> | 2015-06-25 10:50:24 -0700 |
---|---|---|
committer | Yigit Boyar <yboyar@google.com> | 2015-06-30 10:19:08 -0700 |
commit | 731b74f7f44e67312a1fc4161c4e0aae221b2417 (patch) | |
tree | 62ff3308d8e30d00568db694469c54caacff90d1 /compilationTests | |
parent | 4df4ba38a62b791bbbc25e923efe8d9c2f9a52e9 (diff) | |
download | data-binding-731b74f7f44e67312a1fc4161c4e0aae221b2417.tar.gz |
Introduce Scopes to track logical stack traces
This CL introduces a static class called Scope, which is
used the logical processing stack for data binding.
These scopes are used to generate meaningful error messages
when an error is detected.
Bug: 21953001
Change-Id: I5470a8c4ad94401d34a140762baae9d53c5a0402
Diffstat (limited to 'compilationTests')
4 files changed, 171 insertions, 12 deletions
diff --git a/compilationTests/build.gradle b/compilationTests/build.gradle index abe046ac..e8b56dd9 100644 --- a/compilationTests/build.gradle +++ b/compilationTests/build.gradle @@ -12,4 +12,5 @@ dependencies { testCompile 'org.apache.commons:commons-lang3:3.3.2' testCompile 'commons-io:commons-io:2.4' testCompile 'commons-codec:commons-codec:1.10' + testCompile project(':compilerCommon') }
\ No newline at end of file diff --git a/compilationTests/src/test/java/android/databinding/compilationTest/BaseCompilationTest.java b/compilationTests/src/test/java/android/databinding/compilationTest/BaseCompilationTest.java index c390edcc..51925d58 100644 --- a/compilationTests/src/test/java/android/databinding/compilationTest/BaseCompilationTest.java +++ b/compilationTests/src/test/java/android/databinding/compilationTest/BaseCompilationTest.java @@ -19,6 +19,10 @@ package android.databinding.compilationTest; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TestName; + +import android.databinding.tool.store.Location; import java.io.File; import java.io.FileOutputStream; @@ -29,8 +33,11 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermission; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; @@ -42,6 +49,8 @@ import static org.junit.Assert.assertTrue; public class BaseCompilationTest { + @Rule + public TestName name = new TestName(); static Pattern VARIABLES = Pattern.compile("!@\\{([A-Za-z0-9_-]*)}"); public static final String KEY_MANIFEST_PACKAGE = "PACKAGE"; @@ -49,7 +58,7 @@ public class BaseCompilationTest { public static final String KEY_SETTINGS_INCLUDES = "SETTINGS_INCLUDES"; public static final String DEFAULT_APP_PACKAGE = "com.android.databinding.compilationTest.test"; - File testFolder = new File("./build/build-test"); + protected final File testFolder = new File("./build/build-test"); protected void copyResourceTo(String name, String path) throws IOException { copyResourceTo(name, new File(testFolder, path)); @@ -81,6 +90,36 @@ public class BaseCompilationTest { } } + /** + * Extracts the text in the given location from the the at the given application path. + * @param pathInApp The path, relative to the root of the application under test + * @param location The location to extract + * @return The string that is contained in the given location + * @throws IOException If file is invalid. + */ + protected String extract(String pathInApp, Location location) throws IOException { + File file = new File(testFolder, pathInApp); + assertTrue(file.exists()); + StringBuilder result = new StringBuilder(); + List<String> lines = FileUtils.readLines(file); + for (int i = location.startLine; i <= location.endLine; i ++) { + if (i > location.startLine) { + result.append("\n"); + } + String line = lines.get(i); + int start = 0; + if (i == location.startLine) { + start = location.startOffset; + } + int end = line.length() - 1; // inclusive + if (i == location.endLine) { + end = location.endOffset; + } + result.append(line.substring(start, end + 1)); + } + return result.toString(); + } + protected void copyResourceTo(String name, File targetFile) throws IOException { File directory = targetFile.getParentFile(); FileUtils.forceMkdir(directory); @@ -179,13 +218,17 @@ public class BaseCompilationTest { copyResourceTo("/module_build.gradle", new File(moduleFolder, "build.gradle"), replacements); } - protected CompilationResult runGradle(String params) throws IOException, InterruptedException { + protected CompilationResult runGradle(String... params) throws IOException, InterruptedException { setExecutable(); File pathToExecutable = new File(testFolder, "gradlew"); - ProcessBuilder builder = new ProcessBuilder(pathToExecutable.getAbsolutePath(), params); + List<String> args = new ArrayList<>(); + args.add(pathToExecutable.getAbsolutePath()); + args.add("--project-cache-dir"); + args.add(new File("../.caches/", name.getMethodName()).getAbsolutePath()); + Collections.addAll(args, params); + ProcessBuilder builder = new ProcessBuilder(args); builder.environment().putAll(System.getenv()); builder.directory(testFolder); - //builder.redirectErrorStream(true); // merges error and input streams Process process = builder.start(); String output = IOUtils.toString(process.getInputStream()); String error = IOUtils.toString(process.getErrorStream()); diff --git a/compilationTests/src/test/java/android/databinding/compilationTest/CompilationResult.java b/compilationTests/src/test/java/android/databinding/compilationTest/CompilationResult.java index 496550ba..f50755bd 100644 --- a/compilationTests/src/test/java/android/databinding/compilationTest/CompilationResult.java +++ b/compilationTests/src/test/java/android/databinding/compilationTest/CompilationResult.java @@ -16,6 +16,13 @@ package android.databinding.compilationTest; +import android.databinding.tool.processing.ScopedErrorReport; +import android.databinding.tool.processing.ScopedException; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + public class CompilationResult { public final int resultCode; public final String output; @@ -34,4 +41,17 @@ public class CompilationResult { public boolean errorContainsText(String text) { return resultCode != 0 && error.indexOf(text) > 0; } + + public ScopedException getBindingException() { + List<ScopedException> errors = ScopedException.extractErrors(error); + if (errors.isEmpty()) { + return null; + } + assertEquals(1, errors.size()); + return errors.get(0); + } + + public List<ScopedException> getBindingExceptions() { + return ScopedException.extractErrors(error); + } } diff --git a/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java b/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java index 6540e67d..877fe80a 100644 --- a/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java +++ b/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java @@ -20,13 +20,21 @@ package android.databinding.compilationTest; import org.apache.commons.lang3.StringUtils; import org.junit.Test; +import android.databinding.tool.processing.ErrorMessages; +import android.databinding.tool.processing.ScopedErrorReport; +import android.databinding.tool.processing.ScopedException; +import android.databinding.tool.store.Location; + import java.io.File; import java.io.IOException; import java.net.URISyntaxException; +import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class SimpleCompilationTest extends BaseCompilationTest { @@ -50,17 +58,95 @@ public class SimpleCompilationTest extends BaseCompilationTest { result.resultContainsText("BUILD SUCCESSFUL")); } + private ScopedException singleFileErrorTest(String resource, String targetFile, + String expectedExtract, String errorMessage) + throws IOException, URISyntaxException, InterruptedException { + prepareProject(); + copyResourceTo(resource, targetFile); + CompilationResult result = runGradle("assembleDebug"); + assertNotEquals(0, result.resultCode); + ScopedException scopedException = result.getBindingException(); + assertNotNull(scopedException); + ScopedErrorReport report = scopedException.getScopedErrorReport(); + assertNotNull(report); + assertEquals(1, report.getLocations().size()); + Location loc = report.getLocations().get(0); + if (expectedExtract != null) { + String extract = extract(targetFile, loc); + assertEquals(expectedExtract, extract); + } + final File errorFile = new File(report.getFilePath()); + assertTrue(errorFile.exists()); + assertEquals(new File(testFolder, targetFile).getCanonicalFile(), + errorFile.getCanonicalFile()); + if (errorMessage != null) { + assertEquals(errorMessage, scopedException.getBareMessage()); + } + return scopedException; + } + @Test - public void testUndefinedVariable() throws IOException, URISyntaxException, - InterruptedException { + public void testMultipleExceptionsInDifferentFiles() + throws IOException, URISyntaxException, InterruptedException { prepareProject(); copyResourceTo("/layout/undefined_variable_binding.xml", "/app/src/main/res/layout/broken.xml"); + copyResourceTo("/layout/invalid_setter_binding.xml", + "/app/src/main/res/layout/invalid_setter.xml"); CompilationResult result = runGradle("assembleDebug"); assertNotEquals(0, result.resultCode); - assertTrue("Undefined variable", - result.errorContainsText( - "Identifiers must have user defined types from the XML file. myVariable is missing it")); + List<ScopedException> bindingExceptions = result.getBindingExceptions(); + assertEquals(2, bindingExceptions.size()); + File broken = new File(testFolder, "/app/src/main/res/layout/broken.xml"); + File invalidSetter = new File(testFolder, "/app/src/main/res/layout/invalid_setter.xml"); + for (ScopedException exception : bindingExceptions) { + ScopedErrorReport report = exception.getScopedErrorReport(); + final File errorFile = new File(report.getFilePath()); + String message = null; + String expectedErrorFile = null; + if (errorFile.getCanonicalPath().equals(broken.getCanonicalPath())) { + message = String.format(ErrorMessages.UNDEFINED_VARIABLE, "myVariable"); + expectedErrorFile = "/app/src/main/res/layout/broken.xml"; + } else if (errorFile.getCanonicalPath().equals(invalidSetter.getCanonicalPath())) { + message = String.format(ErrorMessages.CANNOT_FIND_SETTER_CALL, "android:textx", + String.class.getCanonicalName()); + expectedErrorFile = "/app/src/main/res/layout/invalid_setter.xml"; + } else { + fail("unexpected exception " + exception.getBareMessage()); + } + assertEquals(1, report.getLocations().size()); + Location loc = report.getLocations().get(0); + String extract = extract(expectedErrorFile, loc); + assertEquals("myVariable", extract); + assertEquals(message, exception.getBareMessage()); + } + } + + @Test + public void testUndefinedVariable() throws IOException, URISyntaxException, + InterruptedException { + ScopedException ex = singleFileErrorTest("/layout/undefined_variable_binding.xml", + "/app/src/main/res/layout/broken.xml", "myVariable", + String.format(ErrorMessages.UNDEFINED_VARIABLE, "myVariable")); + } + + @Test + public void testInvalidSetterBinding() throws IOException, URISyntaxException, + InterruptedException { + prepareProject(); + ScopedException ex = singleFileErrorTest("/layout/invalid_setter_binding.xml", + "/app/src/main/res/layout/invalid_setter.xml", "myVariable", + String.format(ErrorMessages.CANNOT_FIND_SETTER_CALL, "android:textx", + String.class.getCanonicalName())); + } + + @Test + public void testInvalidVariableType() throws IOException, URISyntaxException, + InterruptedException { + prepareProject(); + ScopedException ex = singleFileErrorTest("/layout/invalid_variable_type.xml", + "/app/src/main/res/layout/invalid_variable.xml", "myVariable", + String.format(ErrorMessages.CANNOT_RESOLVE_TYPE, "myVariable~")); } @Test @@ -96,8 +182,17 @@ public class SimpleCompilationTest extends BaseCompilationTest { copyResourceTo("/layout/merge_include.xml", "/app/src/main/res/layout/merge_include.xml"); CompilationResult result = runGradle("assembleDebug"); assertNotEquals(0, result.resultCode); - assertTrue("Merge shouldn't support includes as root. Error message was '" + result.error + "'", - result.errorContainsText( - "Data binding does not support include elements as direct children of a merge element")); + List<ScopedException> errors = ScopedException.extractErrors(result.error); + assertEquals(result.error, 1, errors.size()); + final ScopedException ex = errors.get(0); + final ScopedErrorReport report = ex.getScopedErrorReport(); + final File errorFile = new File(report.getFilePath()); + assertTrue(errorFile.exists()); + assertEquals( + new File(testFolder, "/app/src/main/res/layout/merge_include.xml") + .getCanonicalFile(), + errorFile.getCanonicalFile()); + assertEquals("Merge shouldn't support includes as root. Error message was '" + result.error, + ErrorMessages.INCLUDE_INSIDE_MERGE, ex.getBareMessage()); } } |