Skip to content

Commit

Permalink
Add BaseEmulatorHelper class and tests, refactor helper classes (#1377)
Browse files Browse the repository at this point in the history
  • Loading branch information
mziccard authored Nov 9, 2016
1 parent e5f9046 commit c9e1630
Show file tree
Hide file tree
Showing 14 changed files with 1,466 additions and 688 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright 2016 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 com.google.cloud.testing;

import static com.google.common.base.MoreObjects.firstNonNull;

import com.google.common.base.Strings;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* This class allows to read a process output stream, block until a provided string appears on the
* stream and redirect pertinent error logs to a provided logger.
*/
class BlockingProcessStreamReader extends Thread {

private static final int STREAM_READER_SLEEP_INTERVAL_IN_MS = 200;
private static final int LOG_LENGTH_LIMIT = 50000;

private final BufferedReader errorReader;
private final Logger logger;
private StringBuilder currentLog;
private Level currentLogLevel;
private boolean collectionMode;
private volatile boolean terminated;
private final String emulatorTag;
private final Pattern logLinePattern;

private BlockingProcessStreamReader(String emulator, InputStream stream, String blockUntil,
Logger logger) throws IOException {
super("blocking-process-stream-reader");
setDaemon(true);
errorReader = new BufferedReader(new InputStreamReader(stream));
this.logger = logger;
this.emulatorTag = "[" + emulator + "]";
this.logLinePattern = Pattern.compile("(\\[" + emulator + "\\]\\s)?(\\w+):.*");
if (!Strings.isNullOrEmpty(blockUntil)) {
String line;
do {
line = errorReader.readLine();
} while (line != null && !line.contains(blockUntil));
}
}

void terminate() throws IOException {
terminated = true;
errorReader.close();
interrupt();
}

@Override
public void run() {
String previousLine = "";
String nextLine = "";
while (!terminated) {
try {
if (errorReader.ready()) {
previousLine = nextLine;
nextLine = errorReader.readLine();
if (nextLine == null) {
terminated = true;
} else {
processLogLine(previousLine, nextLine);
}
} else {
sleep(STREAM_READER_SLEEP_INTERVAL_IN_MS);
}
} catch (IOException e) {
e.printStackTrace(System.err);
} catch (InterruptedException e) {
previousLine = nextLine;
nextLine = null;
break;
}
}
processLogLine(previousLine, firstNonNull(nextLine, ""));
writeLog();
}

private void processLogLine(String previousLine, String nextLine) {
// Each log is two lines with the following format:
// [Emulator]? [Date] [Time] [LoggingClass] [method]
// [Emulator]? [LEVEL]: error message
// [Emulator]? more data
// Exceptions and stack traces are included in error stream, separated by a newline
Level nextLogLevel = getLevel(nextLine);
if (nextLogLevel != null) {
writeLog();
currentLog = new StringBuilder();
currentLogLevel = nextLogLevel;
collectionMode = true;
} else if (collectionMode) {
if (currentLog.length() > LOG_LENGTH_LIMIT) {
collectionMode = false;
} else if (currentLog.length() == 0) {
// strip level out of the line
currentLog.append(emulatorTag);
currentLog.append(previousLine.split(":", 2)[1]);
currentLog.append(System.getProperty("line.separator"));
} else {
if (!previousLine.startsWith(emulatorTag)) {
currentLog.append(emulatorTag);
currentLog.append(' ');
}
currentLog.append(previousLine);
currentLog.append(System.getProperty("line.separator"));
}
}
}

private void writeLog() {
if (currentLogLevel != null && currentLog != null && currentLog.length() != 0) {
logger.log(currentLogLevel, currentLog.toString().trim());
}
}

private Level getLevel(String line) {
try {
Matcher matcher = logLinePattern.matcher(line);
if (matcher.matches()) {
return Level.parse(matcher.group(2));
} else {
return null;
}
} catch (IllegalArgumentException e) {
return null; // level wasn't supplied in this log line
}
}

static BlockingProcessStreamReader start(String emulator, InputStream stream, String blockUntil,
Logger logger) throws IOException {
BlockingProcessStreamReader thread =
new BlockingProcessStreamReader(emulator, stream, blockUntil, logger);
thread.start();
return thread;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2016 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 com.google.cloud.testing;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

/**
* Utility class that executes system commands on both Windows and Unix.
*/
class CommandWrapper {

private final List<String> prefix;
private List<String> command;
private String nullFilename;
private boolean redirectOutputToNull;
private boolean redirectErrorStream;
private boolean redirectErrorInherit;
private Path directory;

private CommandWrapper() {
this.prefix = new ArrayList<>();
if (BaseEmulatorHelper.isWindows()) {
this.prefix.add("cmd");
this.prefix.add("/C");
this.nullFilename = "NUL:";
} else {
this.prefix.add("bash");
this.nullFilename = "/dev/null";
}
}

CommandWrapper setCommand(List<String> command) {
this.command = new ArrayList<>(command.size() + this.prefix.size());
this.command.addAll(prefix);
this.command.addAll(command);
return this;
}

CommandWrapper setRedirectOutputToNull() {
this.redirectOutputToNull = true;
return this;
}

CommandWrapper setRedirectErrorStream() {
this.redirectErrorStream = true;
return this;
}

CommandWrapper setRedirectErrorInherit() {
this.redirectErrorInherit = true;
return this;
}

CommandWrapper setDirectory(Path directory) {
this.directory = directory;
return this;
}

ProcessBuilder getBuilder() {
ProcessBuilder builder = new ProcessBuilder(command);
if (redirectOutputToNull) {
builder.redirectOutput(new File(nullFilename));
}
if (directory != null) {
builder.directory(directory.toFile());
}
if (redirectErrorStream) {
builder.redirectErrorStream(true);
}
if (redirectErrorInherit) {
builder.redirectError(ProcessBuilder.Redirect.INHERIT);
}
return builder;
}

public Process start() throws IOException {
return getBuilder().start();
}

static CommandWrapper create() {
return new CommandWrapper();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2016 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 com.google.cloud.testing;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Simplified wrapper for emulator's versions.
*/
class Version implements Comparable<Version> {

private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)$");

private final int major;
private final int minor;
private final int patch;

private Version(int major, int minor, int patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
}

@Override
public int compareTo(Version version) {
int result = major - version.major;
if (result == 0) {
result = minor - version.minor;
if (result == 0) {
result = patch - version.patch;
}
}
return result;
}

@Override
public String toString() {
return String.format("%d.%d.%d", major, minor, patch);
}

@Override
public boolean equals(Object other) {
return this == other || other instanceof Version && compareTo((Version) other) == 0;
}

@Override
public int hashCode() {
return Objects.hash(major, minor, patch);
}

int getMajor() {
return major;
}

int getMinor() {
return minor;
}

int getPatch() {
return patch;
}

static Version fromString(String version) {
Matcher matcher = VERSION_PATTERN.matcher(checkNotNull(version));
if (matcher.matches()) {
return new Version(
Integer.valueOf(matcher.group(1)),
Integer.valueOf(matcher.group(2)),
Integer.valueOf(matcher.group(3)));
}
throw new IllegalArgumentException("Invalid version format");
}
}
Loading

0 comments on commit c9e1630

Please sign in to comment.