/*
* 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.testutil.integration;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.martiansoftware.nailgun.NGContext;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.tools.ToolProvider;
import org.ini4j.Ini;
/**
* Utility to locate the {@code testdata} directory relative to an integration test.
*
* <p>Integration tests will often have a sample project layout in a directory under a directory
* named {@code testdata}. The name of the directory should correspond to the scenario being tested.
*/
public class TestDataHelper {
private static final Splitter JAVA_PACKAGE_SPLITTER = Splitter.on('.');
private static final String TEST_DIRECTORY = new File("test").getAbsolutePath();
private static final String TESTDATA_DIRECTORY_NAME = "testdata";
/** Utility class: do not instantiate. */
private TestDataHelper() {}
public static Path getTestDataDirectory(Object testCase) {
return getTestDataDirectory(testCase.getClass());
}
public static Path getTestDataDirectory(Class<?> testCaseClass) {
String javaPackage = testCaseClass.getPackage().getName();
List<String> parts = new ArrayList<>();
for (String component : JAVA_PACKAGE_SPLITTER.split(javaPackage)) {
parts.add(component);
}
parts.add(TESTDATA_DIRECTORY_NAME);
String[] directories = parts.toArray(new String[0]);
Path result = FileSystems.getDefault().getPath(TEST_DIRECTORY, directories);
// If we're running this test in IJ, then this path doesn't exist. Fall back to one that does
if (!Files.exists(result)) {
result =
Paths.get("test")
.resolve(testCaseClass.getPackage().getName().replace('.', '/'))
.resolve("testdata");
}
return result;
}
public static Path getTestDataScenario(Object testCase, String scenario) {
return getTestDataDirectory(testCase).resolve(scenario);
}
public static ProjectWorkspace createProjectWorkspaceForScenario(
Object testCase, String scenario, TemporaryPaths temporaryRoot) {
Path templateDir = TestDataHelper.getTestDataScenario(testCase, scenario);
return new CacheClearingProjectWorkspace(templateDir, temporaryRoot.getRoot());
}
public static ProjectWorkspace createProjectWorkspaceForScenario(
Object testCase, String scenario, Path temporaryRoot) {
Path templateDir = TestDataHelper.getTestDataScenario(testCase, scenario);
return new CacheClearingProjectWorkspace(templateDir, temporaryRoot);
}
public static void overrideBuckconfig(
ProjectWorkspace projectWorkspace,
Map<String, ? extends Map<String, String>> buckconfigOverrides)
throws IOException {
String config = projectWorkspace.getFileContents(".buckconfig");
Ini ini = new Ini(new StringReader(config));
for (Map.Entry<String, ? extends Map<String, String>> section :
buckconfigOverrides.entrySet()) {
for (Map.Entry<String, String> entry : section.getValue().entrySet()) {
ini.put(section.getKey(), entry.getKey(), entry.getValue());
}
}
StringWriter writer = new StringWriter();
ini.store(writer);
Files.write(projectWorkspace.getPath(".buckconfig"), writer.toString().getBytes(UTF_8));
}
private static class CacheClearingProjectWorkspace extends ProjectWorkspace {
public CacheClearingProjectWorkspace(Path templateDir, Path targetFolder) {
super(templateDir, targetFolder);
}
@Override
public ProcessResult runBuckCommandWithEnvironmentOverridesAndContext(
Path repoRoot,
Optional<NGContext> context,
ImmutableMap<String, String> environmentOverrides,
String... args)
throws IOException {
ProcessResult result =
super.runBuckCommandWithEnvironmentOverridesAndContext(
repoRoot, context, environmentOverrides, args);
// javac has a global cache of zip/jar file content listings. It determines the validity of
// a given cache entry based on the modification time of the zip file in question. In normal
// usage, this is fine. However, in tests, we often will do a build, change something, and
// then rapidly do another build. If this happens quickly, javac can be operating from
// incorrect information when reading a jar file, resulting in "bad class file" or
// "corrupted zip file" errors. We work around this for testing purposes by reaching inside
// the compiler and clearing the cache.
try {
Class<?> cacheClass =
Class.forName(
"com.sun.tools.javac.file.ZipFileIndexCache",
false,
ToolProvider.getSystemToolClassLoader());
Method getSharedInstanceMethod = cacheClass.getMethod("getSharedInstance");
Method clearCacheMethod = cacheClass.getMethod("clearCache");
Object cache = getSharedInstanceMethod.invoke(cacheClass);
clearCacheMethod.invoke(cache);
return result;
} catch (ClassNotFoundException
| IllegalAccessException
| InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
}