Skip to content

Commit 9216288

Browse files
committed
Add BaseEmulatorHelper class and tests, refactor helper classes
1 parent e5f9046 commit 9216288

File tree

14 files changed

+1466
-688
lines changed

14 files changed

+1466
-688
lines changed

google-cloud-core/src/main/java/com/google/cloud/testing/BaseEmulatorHelper.java

Lines changed: 428 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.testing;
18+
19+
import static com.google.common.base.MoreObjects.firstNonNull;
20+
21+
import com.google.common.base.Strings;
22+
23+
import java.io.BufferedReader;
24+
import java.io.IOException;
25+
import java.io.InputStream;
26+
import java.io.InputStreamReader;
27+
import java.util.logging.Level;
28+
import java.util.logging.Logger;
29+
import java.util.regex.Matcher;
30+
import java.util.regex.Pattern;
31+
32+
/**
33+
* This class allows to read a process output stream, block until a provided string appears on the
34+
* stream and redirect pertinent error logs to a provided logger.
35+
*/
36+
class BlockingProcessStreamReader extends Thread {
37+
38+
private static final int STREAM_READER_SLEEP_INTERVAL_IN_MS = 200;
39+
private static final int LOG_LENGTH_LIMIT = 50000;
40+
41+
private final BufferedReader errorReader;
42+
private final Logger logger;
43+
private StringBuilder currentLog;
44+
private Level currentLogLevel;
45+
private boolean collectionMode;
46+
private volatile boolean terminated;
47+
private final String emulatorTag;
48+
private final Pattern logLinePattern;
49+
50+
private BlockingProcessStreamReader(String emulator, InputStream stream, String blockUntil,
51+
Logger logger) throws IOException {
52+
super("blocking-process-stream-reader");
53+
setDaemon(true);
54+
errorReader = new BufferedReader(new InputStreamReader(stream));
55+
this.logger = logger;
56+
this.emulatorTag = "[" + emulator + "]";
57+
this.logLinePattern = Pattern.compile("(\\[" + emulator + "\\]\\s)?(\\w+):.*");
58+
if (!Strings.isNullOrEmpty(blockUntil)) {
59+
String line;
60+
do {
61+
line = errorReader.readLine();
62+
} while (line != null && !line.contains(blockUntil));
63+
}
64+
}
65+
66+
void terminate() throws IOException {
67+
terminated = true;
68+
errorReader.close();
69+
interrupt();
70+
}
71+
72+
@Override
73+
public void run() {
74+
String previousLine = "";
75+
String nextLine = "";
76+
while (!terminated) {
77+
try {
78+
if (errorReader.ready()) {
79+
previousLine = nextLine;
80+
nextLine = errorReader.readLine();
81+
if (nextLine == null) {
82+
terminated = true;
83+
} else {
84+
processLogLine(previousLine, nextLine);
85+
}
86+
} else {
87+
sleep(STREAM_READER_SLEEP_INTERVAL_IN_MS);
88+
}
89+
} catch (IOException e) {
90+
e.printStackTrace(System.err);
91+
} catch (InterruptedException e) {
92+
previousLine = nextLine;
93+
nextLine = null;
94+
break;
95+
}
96+
}
97+
processLogLine(previousLine, firstNonNull(nextLine, ""));
98+
writeLog();
99+
}
100+
101+
private void processLogLine(String previousLine, String nextLine) {
102+
// Each log is two lines with the following format:
103+
// [Emulator]? [Date] [Time] [LoggingClass] [method]
104+
// [Emulator]? [LEVEL]: error message
105+
// [Emulator]? more data
106+
// Exceptions and stack traces are included in error stream, separated by a newline
107+
Level nextLogLevel = getLevel(nextLine);
108+
if (nextLogLevel != null) {
109+
writeLog();
110+
currentLog = new StringBuilder();
111+
currentLogLevel = nextLogLevel;
112+
collectionMode = true;
113+
} else if (collectionMode) {
114+
if (currentLog.length() > LOG_LENGTH_LIMIT) {
115+
collectionMode = false;
116+
} else if (currentLog.length() == 0) {
117+
// strip level out of the line
118+
currentLog.append(emulatorTag);
119+
currentLog.append(previousLine.split(":", 2)[1]);
120+
currentLog.append(System.getProperty("line.separator"));
121+
} else {
122+
if (!previousLine.startsWith(emulatorTag)) {
123+
currentLog.append(emulatorTag);
124+
currentLog.append(" ");
125+
}
126+
currentLog.append(previousLine);
127+
currentLog.append(System.getProperty("line.separator"));
128+
}
129+
}
130+
}
131+
132+
private void writeLog() {
133+
if (currentLogLevel != null && currentLog != null && currentLog.length() != 0) {
134+
logger.log(currentLogLevel, currentLog.toString().trim());
135+
}
136+
}
137+
138+
private Level getLevel(String line) {
139+
try {
140+
Matcher matcher = logLinePattern.matcher(line);
141+
if (matcher.matches()) {
142+
return Level.parse(matcher.group(2));
143+
} else {
144+
return null;
145+
}
146+
} catch (IllegalArgumentException e) {
147+
return null; // level wasn't supplied in this log line
148+
}
149+
}
150+
151+
static BlockingProcessStreamReader start(String emulator, InputStream stream, String blockUntil,
152+
Logger logger) throws IOException {
153+
BlockingProcessStreamReader thread =
154+
new BlockingProcessStreamReader(emulator, stream, blockUntil, logger);
155+
thread.start();
156+
return thread;
157+
}
158+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.testing;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.nio.file.Path;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
25+
/**
26+
* Utility class that executes system commands on both Windows and Unix.
27+
*/
28+
class CommandWrapper {
29+
30+
private final List<String> prefix;
31+
private List<String> command;
32+
private String nullFilename;
33+
private boolean redirectOutputToNull;
34+
private boolean redirectErrorStream;
35+
private boolean redirectErrorInherit;
36+
private Path directory;
37+
38+
private CommandWrapper() {
39+
this.prefix = new ArrayList<>();
40+
if (BaseEmulatorHelper.isWindows()) {
41+
this.prefix.add("cmd");
42+
this.prefix.add("/C");
43+
this.nullFilename = "NUL:";
44+
} else {
45+
this.prefix.add("bash");
46+
this.nullFilename = "/dev/null";
47+
}
48+
}
49+
50+
CommandWrapper setCommand(List<String> command) {
51+
this.command = new ArrayList<>(command.size() + this.prefix.size());
52+
this.command.addAll(prefix);
53+
this.command.addAll(command);
54+
return this;
55+
}
56+
57+
CommandWrapper setRedirectOutputToNull() {
58+
this.redirectOutputToNull = true;
59+
return this;
60+
}
61+
62+
CommandWrapper setRedirectErrorStream() {
63+
this.redirectErrorStream = true;
64+
return this;
65+
}
66+
67+
CommandWrapper setRedirectErrorInherit() {
68+
this.redirectErrorInherit = true;
69+
return this;
70+
}
71+
72+
CommandWrapper setDirectory(Path directory) {
73+
this.directory = directory;
74+
return this;
75+
}
76+
77+
ProcessBuilder getBuilder() {
78+
ProcessBuilder builder = new ProcessBuilder(command);
79+
if (redirectOutputToNull) {
80+
builder.redirectOutput(new File(nullFilename));
81+
}
82+
if (directory != null) {
83+
builder.directory(directory.toFile());
84+
}
85+
if (redirectErrorStream) {
86+
builder.redirectErrorStream(true);
87+
}
88+
if (redirectErrorInherit) {
89+
builder.redirectError(ProcessBuilder.Redirect.INHERIT);
90+
}
91+
return builder;
92+
}
93+
94+
public Process start() throws IOException {
95+
return getBuilder().start();
96+
}
97+
98+
static CommandWrapper create() {
99+
return new CommandWrapper();
100+
}
101+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
18+
package com.google.cloud.testing;
19+
20+
import static com.google.common.base.Preconditions.checkNotNull;
21+
22+
import java.util.Objects;
23+
import java.util.regex.Matcher;
24+
import java.util.regex.Pattern;
25+
26+
/**
27+
* Simplified wrapper for emulator's versions.
28+
*/
29+
class Version implements Comparable<Version> {
30+
31+
private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)$");
32+
33+
private final int major;
34+
private final int minor;
35+
private final int patch;
36+
37+
private Version(int major, int minor, int patch) {
38+
this.major = major;
39+
this.minor = minor;
40+
this.patch = patch;
41+
}
42+
43+
@Override
44+
public int compareTo(Version version) {
45+
int result = major - version.major;
46+
if (result == 0) {
47+
result = minor - version.minor;
48+
if (result == 0) {
49+
result = patch - version.patch;
50+
}
51+
}
52+
return result;
53+
}
54+
55+
@Override
56+
public String toString() {
57+
return String.format("%d.%d.%d", major, minor, patch);
58+
}
59+
60+
@Override
61+
public boolean equals(Object other) {
62+
return this == other || other instanceof Version && compareTo((Version) other) == 0;
63+
}
64+
65+
@Override
66+
public int hashCode() {
67+
return Objects.hash(major, minor, patch);
68+
}
69+
70+
int getMajor() {
71+
return major;
72+
}
73+
74+
int getMinor() {
75+
return minor;
76+
}
77+
78+
int getPatch() {
79+
return patch;
80+
}
81+
82+
static Version fromString(String version) {
83+
Matcher matcher = VERSION_PATTERN.matcher(checkNotNull(version));
84+
if (matcher.matches()) {
85+
return new Version(
86+
Integer.valueOf(matcher.group(1)),
87+
Integer.valueOf(matcher.group(2)),
88+
Integer.valueOf(matcher.group(3)));
89+
}
90+
throw new IllegalArgumentException("Invalid version format");
91+
}
92+
}

0 commit comments

Comments
 (0)