blob: 3e5ac188825476c1260378c51386c3912e147d03 [file] [log] [blame]
/*
* Copyright 2012-present Facebook, Inc.
*
* 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.facebook.buck.junit;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import com.facebook.buck.testutil.integration.DebuggableTemporaryFolder;
import com.facebook.buck.testutil.integration.ProjectWorkspace;
import com.facebook.buck.testutil.integration.ProjectWorkspace.ProcessResult;
import com.facebook.buck.testutil.integration.TestDataHelper;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.io.Files;
import org.junit.Rule;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.List;
/**
* Integration test to verify that timeouts are handled as expected by {@link JUnitRunner}.
* <p>
* Note that there is a quirk when running tests with threads and timeouts described at
* https://github.com/junit-team/junit/issues/686. This test verifies that Buck honors this behavior
* of JUnit with its custom {@link BuckBlockJUnit4ClassRunner}.
* <p>
* That said, this behavior of JUnit interacts badly with a default test timeout in
* {@code .buckconfig} because it requires adding {@link org.junit.rules.Timeout} to the handful of
* tests that exploit this behavior.
*/
public class TimeoutIntegrationTest {
private static final String PATH_TO_TIMEOUT_BEHAVIOR_TEST = "TimeoutChangesBehaviorTest.java";
@Rule
public DebuggableTemporaryFolder temporaryFolder = new DebuggableTemporaryFolder();
@Test
public void testThatTimeoutsInTestsWorkAsExpected() throws IOException {
ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "timeouts", temporaryFolder);
workspace.setUp();
// ExceedsAnnotationTimeoutTest should fail.
ProcessResult exceedsAnnotationTimeoutTestResult = workspace.runBuckCommand(
"test", "//:ExceedsAnnotationTimeoutTest");
exceedsAnnotationTimeoutTestResult.assertExitCode("Test should fail due to timeout", 1);
assertThat(exceedsAnnotationTimeoutTestResult.getStderr(),
containsString(
"FAILURE testShouldFailDueToExpiredTimeout: test timed out after 1000 milliseconds"));
// TimeoutChangesBehaviorTest should pass.
ProcessResult timeoutTestWithoutTimeout = workspace.runBuckCommand(
"test", "//:TimeoutChangesBehaviorTest");
timeoutTestWithoutTimeout.assertExitCode(0);
// TimeoutChangesBehaviorTest with @Test(timeout) specified should fail.
// See https://github.com/junit-team/junit/issues/686 about why it fails.
modifyTimeoutInTestAnnotation(PATH_TO_TIMEOUT_BEHAVIOR_TEST, /* addTimeout */ true);
ProcessResult timeoutTestWithTimeoutOnAnnotation = workspace.runBuckCommand(
"test", "//:TimeoutChangesBehaviorTest");
timeoutTestWithTimeoutOnAnnotation.assertExitCode(1);
assertThat(timeoutTestWithTimeoutOnAnnotation.getStderr(),
containsString(
"FAILURE testTimeoutDictatesTheSuccessOfThisTest: " +
"Database should have an open transaction due to setUp()."));
// TimeoutChangesBehaviorTest with @Rule(Timeout) should pass.
modifyTimeoutInTestAnnotation(PATH_TO_TIMEOUT_BEHAVIOR_TEST, /* addTimeout */ false);
insertTimeoutRule(PATH_TO_TIMEOUT_BEHAVIOR_TEST);
ProcessResult timeoutTestWithTimeoutRule = workspace.runBuckCommand(
"test", "//:TimeoutChangesBehaviorTest");
timeoutTestWithTimeoutRule.assertExitCode(0);
workspace.verify();
}
/**
* Swaps all instances of {@code @Test} with {@code @Test(timeout = 10000)} in the specified Java
* file, as determined by the value of {@code addTimeout}.
*/
private void modifyTimeoutInTestAnnotation(String path, final boolean addTimeout)
throws IOException {
Function<String, String> transform = new Function<String, String>() {
@Override public String apply(String line) {
String original = addTimeout ? "@Test" : "@Test(timeout = 100000)";
String replacement = addTimeout ? "@Test(timeout = 100000)" : "@Test";
return line.replace(original, replacement) + '\n';
}
};
rewriteFileWithTransform(path, transform);
}
/**
* Inserts the following after the top-level class declaration:
* <pre>
* @org.junit.Rule
* public org.junit.rules.Timeout timeoutForTests = new org.junit.rules.Timeout(10000);
* </pre>
*/
private void insertTimeoutRule(String path) throws IOException {
Function<String, String> transform = new Function<String, String>() {
@Override
public String apply(String line) {
if (line.startsWith("public class")) {
return line + "\n\n" +
" @org.junit.Rule\n" +
" public org.junit.rules.Timeout timeoutForTests = " +
"new org.junit.rules.Timeout(10000);\n";
} else {
return line + '\n';
}
}
};
rewriteFileWithTransform(path, transform);
}
/**
* Finds the file at the specified path, transforms all of its lines using the specified
* {@code transform} parameter, and writes the transformed lines back to the path.
*/
private void rewriteFileWithTransform(String path, Function<String, String> transform)
throws IOException {
File javaFile = new File(temporaryFolder.getRoot(), path);
List<String> lines = Files.readLines(javaFile, Charsets.UTF_8);
String java = Joiner.on("").join(Iterables.transform(lines, transform));
Files.write(java, javaFile, Charsets.UTF_8);
}
}