/*
* Copyright 2014 Google Inc. All rights reserved.
*
* 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 org.inferred.freebuilder.processor.util;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasProperty;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.inferred.freebuilder.processor.util.testing.ModelRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import javax.annotation.processing.Filer;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
/** Tests for {@link FilerUtils}. */
@RunWith(MockitoJUnitRunner.class)
public class FilerUtilsTest {
private static final QualifiedName CLASS_TO_WRITE = QualifiedName.of("com.example", "bar");
@Rule public final ModelRule model = new ModelRule();
@Rule public final ExpectedException thrown = ExpectedException.none();
@Mock private Filer filer;
@Mock private JavaFileObject sourceFile;
private final StringWriter source = new StringWriter();
private TypeElement originatingElement;
@Before
public void setup() throws IOException {
originatingElement = model.newType("package com.example; public class Foo { }");
when(filer.createSourceFile(eq("com.example.bar"), (Element[]) any())).thenReturn(sourceFile);
when(sourceFile.openWriter()).thenReturn(source);
}
@Test
public void testSimplePath() throws IOException {
FilerUtils.writeCompilationUnit(filer, CLASS_TO_WRITE, originatingElement, "Hello!");
assertEquals("Hello!", source.toString());
}
@Test
public void testConstructor_avoidsEclipseWriterBug() throws IOException {
// Due to a bug in Eclipse, we *must* call close on the object returned from openWriter().
// Eclipse proxies a Writer but does not implement the fluent API correctly.
// Here, we implement the fluent Writer API with the same bug:
Writer mockWriter = Mockito.mock(Writer.class, new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
if (Writer.class.isAssignableFrom(invocation.getMethod().getReturnType())) {
// Erroneously return the delegate writer (matching the Eclipse bug!)
return source;
} else {
return Answers.RETURNS_SMART_NULLS.get().answer(invocation);
}
}
});
when(sourceFile.openWriter()).thenReturn(mockWriter);
FilerUtils.writeCompilationUnit(filer, CLASS_TO_WRITE, originatingElement, "Hello!");
verify(mockWriter).close();
}
@Test
public void testFailuresSuppressed() throws IOException {
Writer mockWriter = Mockito.mock(Writer.class);
doThrow(new IOException("Error appending")).when(mockWriter).append(any());
doThrow(new IOException("Error closing")).when(mockWriter).close();
when(sourceFile.openWriter()).thenReturn(mockWriter);
thrown.expect(IOException.class);
thrown.expectMessage("Error appending");
thrown.expect(suppressed(instanceOf(IOException.class)));
thrown.expect(suppressed(hasProperty("message", equalTo("Error closing"))));
FilerUtils.writeCompilationUnit(filer, CLASS_TO_WRITE, originatingElement, "Hello!");
}
private static Matcher<Throwable> suppressed(Matcher<?> matcher) {
return new BaseMatcher<Throwable>() {
@Override
public boolean matches(Object item) {
if (!(item instanceof Throwable)) {
return false;
}
Throwable t = (Throwable) item;
if (t.getSuppressed().length != 1) {
return false;
}
return matcher.matches(t.getSuppressed()[0]);
}
@Override
public void describeMismatch(Object item, Description description) {
if (!(item instanceof Throwable)) {
description.appendValue(item).appendText(" is not an exception");
}
Throwable t = (Throwable) item;
if (t.getSuppressed().length == 0) {
description.appendValue(item).appendText(" has no suppressed exceptions");
} else if (t.getSuppressed().length > 1) {
description.appendValue(item).appendText(" has multiple suppressed exceptions");
}
Throwable suppressed = t.getSuppressed()[0];
if (!matcher.matches(suppressed)) {
description.appendText("has suppressed exception ");
matcher.describeMismatch(suppressed, description);
}
}
@Override
public void describeTo(Description description) {
description.appendText("exception with suppressed ").appendDescriptionOf(matcher);
}
};
}
}