com.google.api.tools.framework.model.testing.TestConfig Maven / Gradle / Ivy
/*
* Copyright (C) 2016 Google 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.google.api.tools.framework.model.testing;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.api.Service;
import com.google.api.tools.framework.model.ConfigSource;
import com.google.api.tools.framework.model.DiagCollector;
import com.google.api.tools.framework.model.Experiments;
import com.google.api.tools.framework.model.ExperimentsImpl;
import com.google.api.tools.framework.model.ExtensionPool;
import com.google.api.tools.framework.model.Model;
import com.google.api.tools.framework.tools.ToolProtoUtil;
import com.google.api.tools.framework.yaml.YamlReader;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.Message;
import com.google.protobuf.TextFormat;
import com.google.protobuf.TextFormat.ParseException;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A class to represent a test api configuration.
*
* Compiles proto sources found via a {@link TestDataLocator} and converts yaml service config
* files. Allows to create a {@link Model} from them.
*/
public class TestConfig {
private static final String PROTOCOL_COMPILER;
static {
if (!com.google.common.base.Strings.isNullOrEmpty(System.getenv("PROTOC_COMPILER"))) {
PROTOCOL_COMPILER = System.getenv("PROTOC_COMPILER");
} else {
PROTOCOL_COMPILER = "protoc";
}
}
private static final Pattern PROTO_IMPORT_PATTERN =
Pattern.compile("\\s*import\\s*(?:public)?\\s*\"(.*)\"");
private static final ExtensionRegistry EXTENSIONS = ToolProtoUtil.getStandardPlatformExtensions();
private final List protoFiles;
private final Path descriptorFile;
private final TestDataLocator testDataLocator;
private final String tempDir;
private final Experiments enabledExperiments;
/**
* Creates a test api. The passed temp dir is managed by the caller; in a test, it is usally
* created by the TemporaryFolder rule of junit. The passed proto files as well as their imports
* must be retrievable via the passed test data locator.
*/
public TestConfig(TestDataLocator testDataLocator, String tempDir, List protoFiles) {
this(testDataLocator, tempDir, protoFiles, ExperimentsImpl.none());
}
/**
* Creates a test api with a list of enabled experiments. The passed temp dir is managed by the
* caller; in a test, it is usally created by the TemporaryFolder rule of junit. The passed proto
* files as well as their imports must be retrievable via the passed test data locator.
*/
public TestConfig(
TestDataLocator testDataLocator,
String tempDir,
List protoFiles,
Experiments enabledExperiments) {
this.testDataLocator = testDataLocator;
this.protoFiles = ImmutableList.copyOf(protoFiles);
this.tempDir = tempDir;
this.enabledExperiments = enabledExperiments;
// Extract all needed proto files.
Set extracted = Sets.newHashSet();
for (String source : protoFiles) {
extractProtoSources(extracted, source);
}
this.descriptorFile = Paths.get(tempDir, "_descriptor.dsc");
compileProtos(tempDir, protoFiles, descriptorFile.toString());
}
/** Returns the test data locator associated with this test config. */
public TestDataLocator getTestDataLocator() {
return testDataLocator;
}
/** Returns the temp directory into which this test config fetches test data. */
public String getTempDir() {
return tempDir;
}
/**
* Reads test data associated with this test api. Uses the {@link TestDataLocator} provided at
* creation time.
*/
public String readTestData(String name) {
URL url = testDataLocator.findTestData(name);
if (url == null) {
throw new IllegalArgumentException(String.format("Cannot find resource '%s'", name));
}
return testDataLocator.readTestData(url);
}
/**
* Copies test data, located via the test data locator, into the temporary directory associated
* with this test config. Returns the content of the test data as a string.
*/
public String copyTestData(String name) {
String content = readTestData(name);
Path targetPath = Paths.get(tempDir, name);
try {
Files.createDirectories(targetPath.getParent());
Files.write(targetPath, content.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new IllegalArgumentException(
String.format("Cannot copy '%s': %s", targetPath, e.getMessage()));
}
return content;
}
/** Copies test data and returns the path pointing to it. */
public Path copyTestDataAndGetPath(String name) {
copyTestData(name);
return Paths.get(tempDir, name);
}
/** Gets the descriptor file generated from the proto sources. */
public Path getDescriptorFile() {
return descriptorFile;
}
/** Returns the file descriptor set generated from the sources of this api. */
public FileDescriptorSet getDescriptor() throws IOException {
return FileDescriptorSet.parseFrom(Files.newInputStream(descriptorFile), EXTENSIONS);
}
/** Parses the config files, in Yaml format. */
public ImmutableList getApiYamlConfigSources(
DiagCollector diag, List configFileNames) {
ImmutableList.Builder builder = ImmutableList.builder();
for (String fileName : configFileNames) {
ConfigSource config = YamlReader.readConfig(diag, fileName, readTestData(fileName));
if (config != null) {
builder.add(config);
}
}
return builder.build();
}
/** Parses the config files, in Yaml format. */
@Deprecated
public ImmutableList getApiYamlConfig(DiagCollector diag, List configFileNames) {
return FluentIterable.from(getApiYamlConfigSources(diag, configFileNames))
.transform(
new Function() {
@Override
public Message apply(ConfigSource input) {
return input.getConfig();
}
})
.toList();
}
/** Parses the config file, in proto text format, and returns it. */
public Service getApiProtoConfig(String configFileName) throws ParseException {
String content = readTestData(configFileName);
Service.Builder builder = Service.newBuilder();
TextFormat.merge(content, builder);
return builder.build();
}
/** Creates a model, based on the provided config files. */
public Model createModel(List configFileNames) {
try {
Model model =
Model.create(getDescriptor(), protoFiles, enabledExperiments, ExtensionPool.EMPTY);
model.setConfigSources(
getApiYamlConfigSources(model.getDiagReporter().getDiagCollector(), configFileNames));
return model;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Collect all needed proto files as resources from the classpath and store them in the temporary
* directory, so protoc can find them.
*/
private void extractProtoSources(Set extracted, String protoFile) {
if (!extracted.add(protoFile)) {
return;
}
String content = copyTestData(protoFile);
Matcher matcher = PROTO_IMPORT_PATTERN.matcher(content);
while (matcher.find()) {
extractProtoSources(extracted, matcher.group(1));
}
}
/** Calls the protocol compiler to compile the given sources into a descriptor. */
protected void compileProtos(String tempDir, List sourceFiles, String outputFile) {
List commandLine = Lists.newArrayList();
commandLine.add(PROTOCOL_COMPILER);
commandLine.add("--include_imports");
commandLine.add("--proto_path=" + tempDir);
commandLine.add("--include_source_info");
commandLine.add("-o");
commandLine.add(outputFile);
for (String source : sourceFiles) {
commandLine.add(Paths.get(tempDir, source).toString());
}
ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
Path output = Paths.get(tempDir, "_protoc.out");
processBuilder.redirectErrorStream(true);
processBuilder.redirectOutput(output.toFile());
try {
Process process = processBuilder.start();
if (process.waitFor() != 0) {
throw new IllegalArgumentException(
String.format(
"proto compilation failed: %s:\n%s",
Joiner.on(" ").join(commandLine), new String(Files.readAllBytes(output), UTF_8)));
}
} catch (Exception e) {
throw new IllegalArgumentException(
String.format("proto compilation failed with internal error: %s", e.getMessage()));
}
}
}