-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add BaseEmulatorHelper class and tests, refactor helper classes (#1377)
- Loading branch information
Showing
14 changed files
with
1,466 additions
and
688 deletions.
There are no files selected for viewing
428 changes: 428 additions & 0 deletions
428
google-cloud-core/src/main/java/com/google/cloud/testing/BaseEmulatorHelper.java
Large diffs are not rendered by default.
Oops, something went wrong.
158 changes: 158 additions & 0 deletions
158
google-cloud-core/src/main/java/com/google/cloud/testing/BlockingProcessStreamReader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
101 changes: 101 additions & 0 deletions
101
google-cloud-core/src/main/java/com/google/cloud/testing/CommandWrapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
92 changes: 92 additions & 0 deletions
92
google-cloud-core/src/main/java/com/google/cloud/testing/Version.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} |
Oops, something went wrong.