blob: bca7bcab1858092a7b4b52236f56ceadc603ebce [file] [log] [blame]
/*
* Copyright 2014 Google LLC
*
* 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.google.auto.value.processor;
import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH;
import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR;
import static com.google.common.truth.Truth.assertThat;
import static java.util.logging.Level.WARNING;
import static org.junit.Assert.fail;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.reflect.Reflection;
import com.google.escapevelocity.Template;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.logging.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for FieldReader.
*
* @author emcmanus@google.com (Éamonn McManus)
*/
@RunWith(JUnit4.class)
public class TemplateVarsTest {
static class HappyVars extends TemplateVars {
Integer integer;
String string;
List<Integer> list;
private static final String IGNORED_STATIC_FINAL = "hatstand";
@Override
Template parsedTemplate() {
return parsedTemplateForString("integer=$integer string=$string list=$list");
}
}
static Template parsedTemplateForString(String string) {
try {
Reader reader = new StringReader(string);
return Template.parseFrom(reader);
} catch (IOException e) {
throw new AssertionError(e);
}
}
@Test
public void testHappy() {
HappyVars happy = new HappyVars();
happy.integer = 23;
happy.string = "wibble";
happy.list = ImmutableList.of(5, 17, 23);
assertThat(HappyVars.IGNORED_STATIC_FINAL).isEqualTo("hatstand"); // avoids unused warning
String expectedText = "integer=23 string=wibble list=[5, 17, 23]";
String actualText = happy.toText();
assertThat(actualText).isEqualTo(expectedText);
}
@Test
public void testUnset() {
HappyVars sad = new HappyVars();
sad.integer = 23;
sad.list = ImmutableList.of(23);
try {
sad.toText();
fail("Did not get expected exception");
} catch (IllegalArgumentException expected) {
}
}
static class SubHappyVars extends HappyVars {
Character character;
@Override
Template parsedTemplate() {
return parsedTemplateForString(
"integer=$integer string=$string list=$list character=$character");
}
}
@Test
public void testSubSub() {
SubHappyVars vars = new SubHappyVars();
vars.integer = 23;
vars.string = "wibble";
vars.list = ImmutableList.of(5, 17, 23);
vars.character = 'ß';
String expectedText = "integer=23 string=wibble list=[5, 17, 23] character=ß";
String actualText = vars.toText();
assertThat(actualText).isEqualTo(expectedText);
}
static class Private extends TemplateVars {
Integer integer;
private String unusedString;
@Override
Template parsedTemplate() {
throw new UnsupportedOperationException();
}
}
@Test
public void testPrivate() {
try {
new Private();
fail("Did not get expected exception");
} catch (IllegalArgumentException expected) {
}
}
static class Static extends TemplateVars {
Integer integer;
static String string;
@Override
Template parsedTemplate() {
throw new UnsupportedOperationException();
}
}
@Test
public void testStatic() {
try {
new Static();
fail("Did not get expected exception");
} catch (IllegalArgumentException expected) {
}
}
static class Primitive extends TemplateVars {
int integer;
String string;
@Override
Template parsedTemplate() {
throw new UnsupportedOperationException();
}
}
@Test
public void testPrimitive() {
try {
new Primitive();
fail("Did not get expected exception");
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testBrokenInputStream_IOException() throws Exception {
doTestBrokenInputStream(new IOException("BrokenInputStream"));
}
@Test
public void testBrokenInputStream_NullPointerException() throws Exception {
doTestBrokenInputStream(new NullPointerException("BrokenInputStream"));
}
@Test
public void testBrokenInputStream_IllegalStateException() throws Exception {
doTestBrokenInputStream(new IllegalStateException("BrokenInputStream"));
}
// This is a complicated test that tries to simulates the failures that are worked around in
// Template.parsedTemplateForResource. Those failures means that the InputStream returned by
// ClassLoader.getResourceAsStream sometimes throws IOException or NullPointerException or
// IllegalStateException while it is being read. To simulate that, we make a second ClassLoader
// with the same configuration as the one that runs this test, and we override getResourceAsStream
// so that it wraps the returned InputStream in a BrokenInputStream, which throws an exception
// after a certain number of characters. We check that that exception was indeed seen, and that
// we did indeed try to read the resource we're interested in, and that we succeeded in loading a
// Template nevertheless.
private void doTestBrokenInputStream(Exception exception) throws Exception {
URLClassLoader shadowLoader = new ShadowLoader(getClass().getClassLoader(), exception);
Runnable brokenInputStreamTest =
(Runnable)
shadowLoader
.loadClass(BrokenInputStreamTest.class.getName())
.getConstructor()
.newInstance();
brokenInputStreamTest.run();
}
private static class ShadowLoader extends URLClassLoader implements Callable<Set<String>> {
private static final Logger logger = Logger.getLogger(ShadowLoader.class.getName());
private final Exception exception;
private final Set<String> result = new TreeSet<String>();
ShadowLoader(ClassLoader original, Exception exception) {
super(getClassPathUrls(original), original.getParent());
this.exception = exception;
}
private static URL[] getClassPathUrls(ClassLoader original) {
return original instanceof URLClassLoader
? ((URLClassLoader) original).getURLs()
: parseJavaClassPath();
}
/**
* Returns the URLs in the class path specified by the {@code java.class.path} {@linkplain
* System#getProperty system property}.
*/
// TODO(b/65488446): Use a new public API.
private static URL[] parseJavaClassPath() {
ImmutableList.Builder<URL> urls = ImmutableList.builder();
for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) {
try {
try {
urls.add(new File(entry).toURI().toURL());
} catch (SecurityException e) { // File.toURI checks to see if the file is a directory
urls.add(new URL("file", null, new File(entry).getAbsolutePath()));
}
} catch (MalformedURLException e) {
logger.log(WARNING, "malformed classpath entry: " + entry, e);
}
}
return urls.build().toArray(new URL[0]);
}
@Override
public Set<String> call() throws Exception {
return result;
}
@Override
public InputStream getResourceAsStream(String resource) {
// Make sure this is actually the resource we are expecting. If we're using JaCoCo or the
// like, we might end up reading some other resource, and we don't want to break that.
if (resource.startsWith("com/google/auto")) {
result.add(resource);
return new BrokenInputStream(super.getResourceAsStream(resource));
} else {
return super.getResourceAsStream(resource);
}
}
private class BrokenInputStream extends InputStream {
private final InputStream original;
private int count = 0;
BrokenInputStream(InputStream original) {
this.original = original;
}
@Override
public int read() throws IOException {
if (++count > 10) {
result.add("threw");
if (exception instanceof IOException) {
throw (IOException) exception;
}
throw (RuntimeException) exception;
}
return original.read();
}
}
}
public static class BrokenInputStreamTest implements Runnable {
@Override
public void run() {
Template template = TemplateVars.parsedTemplateForResource("autovalue.vm");
assertThat(template).isNotNull();
String resourceName =
Reflection.getPackageName(getClass()).replace('.', '/') + "/autovalue.vm";
@SuppressWarnings("unchecked")
Callable<Set<String>> myLoader = (Callable<Set<String>>) getClass().getClassLoader();
try {
Set<String> result = myLoader.call();
assertThat(result).contains(resourceName);
assertThat(result).contains("threw");
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
}