Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
JENKINS-13798 Iterate over system out contents to find SauceOnDemandS…
…essionID lines
  • Loading branch information
rossrowe committed Jun 9, 2012
1 parent 3aaea6d commit 7f9c95a
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 64 deletions.
Expand Up @@ -7,7 +7,7 @@
/**
* @author Ross Rowe
*/
public class JenkinsSauceConnectManager extends SauceConnectTwoManager {
public class HudsonSauceConnectManager extends SauceConnectTwoManager {

@Override
public String getSauceConnectWorkingDirectory() {
Expand Down
Expand Up @@ -16,7 +16,7 @@
/**
* @author Ross Rowe
*/
public class JenkinsSauceLibraryManager extends SauceLibraryManager {
public class HudsonSauceLibraryManager extends SauceLibraryManager {
/**
*
* @param jarFile
Expand Down
Expand Up @@ -13,19 +13,19 @@
/**
* @author Ross Rowe
*/
public class JenkinsSauceManagerFactory {
public class HudsonSauceManagerFactory {

private static final JenkinsSauceManagerFactory INSTANCE = new JenkinsSauceManagerFactory();
private static final HudsonSauceManagerFactory INSTANCE = new HudsonSauceManagerFactory();

private PlexusContainer plexus = null;

private Lock accessLock = new ReentrantLock();

public static JenkinsSauceManagerFactory getInstance() {
public static HudsonSauceManagerFactory getInstance() {
return INSTANCE;
}

private JenkinsSauceManagerFactory() {
private HudsonSauceManagerFactory() {
}

public SauceConnectTwoManager createSauceConnectManager() throws ComponentLookupException {
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/hudson/plugins/sauce_ondemand/PluginImpl.java
Expand Up @@ -24,8 +24,8 @@
package hudson.plugins.sauce_ondemand;

import com.saucelabs.ci.SauceLibraryManager;
import com.saucelabs.hudson.JenkinsSauceLibraryManager;
import com.saucelabs.hudson.JenkinsSauceManagerFactory;
import com.saucelabs.hudson.HudsonSauceLibraryManager;
import com.saucelabs.hudson.HudsonSauceManagerFactory;
import com.saucelabs.rest.Credential;
import com.saucelabs.rest.SauceTunnelFactory;
import hudson.Extension;
Expand Down Expand Up @@ -55,7 +55,7 @@ public class PluginImpl extends Plugin implements Describable<PluginImpl> {

private static final Logger logger = Logger.getLogger(PluginImpl.class);

private SauceLibraryManager libraryManager = new JenkinsSauceLibraryManager();
private SauceLibraryManager libraryManager = new HudsonSauceLibraryManager();
/**
* User name to access Sauce OnDemand.
*/
Expand Down Expand Up @@ -86,7 +86,7 @@ public void start() throws Exception {
Items.XSTREAM.alias("hudson.plugins.sauce_ondemand.SauceOnDemandBuildWrapper", SauceOnDemandBuildWrapper.class);

load();
JenkinsSauceManagerFactory.getInstance().start();
HudsonSauceManagerFactory.getInstance().start();
}

public void setCredential(String username, String apiKey) throws IOException {
Expand Down
Expand Up @@ -28,10 +28,14 @@
import com.saucelabs.ci.BrowserFactory;
import com.saucelabs.ci.sauceconnect.SauceConnectUtils;
import com.saucelabs.ci.sauceconnect.SauceTunnelManager;
import com.saucelabs.hudson.JenkinsSauceManagerFactory;
import com.saucelabs.hudson.HudsonSauceManagerFactory;
import com.saucelabs.rest.Credential;
import com.saucelabs.rest.JobFactory;
import com.saucelabs.rest.UpdateJob;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.console.LineTransformationOutputStream;
import hudson.model.*;
import hudson.remoting.Callable;
import hudson.tasks.BuildWrapper;
Expand All @@ -46,11 +50,15 @@

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -93,6 +101,7 @@ public class SauceOnDemandBuildWrapper extends BuildWrapper implements Serializa
private static final String SELENIUM_BROWSER = "SELENIUM_BROWSER";
private static final String SELENIUM_PLATFORM = "SELENIUM_PLATFORM";
private static final String SELENIUM_VERSION = "SELENIUM_VERSION";
private SauceOnDemandLogParser logParser;


@DataBoundConstructor
Expand Down Expand Up @@ -205,11 +214,40 @@ public boolean tearDown(AbstractBuild build, BuildListener listener) throws IOEx
tunnelCloser.call();
}
}
processBuildOutput(build);
return true;
}
};
}

private void processBuildOutput(AbstractBuild build) {
JobFactory factory = new JobFactory(new Credential(getUserName(), getApiKey()));
//todo we only want to iterate over lines if we're not using junit test results
String[] array = logParser.getLines().toArray(new String[logParser.getLines().size()]);
List<String[]> sessionIDs = SauceOnDemandReportFactory.findSessionIDs(null, array);

for (String[] sessionId : sessionIDs) {
String id = sessionId[0];
try {

String jobName = sessionId[1];
if (StringUtils.isNotBlank(jobName)) {
factory.update(id,
new UpdateJob(
jobName,
false,
Collections.<String>emptyList(),
Integer.toString(build.getNumber()),
true,//todo how can we tell if the build has passed or failed here?
Collections.<String, Object>emptyMap()));
}
} catch (IOException e) {
logger.log(Level.WARNING, "Error while updating job " + id, e);
}
}

}

private String getCurrentHostName() {
try {
return InetAddress.getLocalHost().getHostName();
Expand Down Expand Up @@ -285,24 +323,24 @@ public String getUserName() {
}

public String getApiKey() {
if (getCredentials() != null) {
return getCredentials().getApiKey();
} else {
PluginImpl p = PluginImpl.get();
if (p.isReuseSauceAuth()) {
com.saucelabs.rest.Credential storedCredentials;
try {
storedCredentials = new com.saucelabs.rest.Credential();
return storedCredentials.getKey();
} catch (IOException e) {
logger.log(Level.WARNING, "Error retrieving credentials", e);
}
} else {
return Secret.toString(p.getApiKey());
if (getCredentials() != null) {
return getCredentials().getApiKey();
} else {
PluginImpl p = PluginImpl.get();
if (p.isReuseSauceAuth()) {
com.saucelabs.rest.Credential storedCredentials;
try {
storedCredentials = new com.saucelabs.rest.Credential();
return storedCredentials.getKey();
} catch (IOException e) {
logger.log(Level.WARNING, "Error retrieving credentials", e);
}
} else {
return Secret.toString(p.getApiKey());
}
return "";
}
return "";
}

public String getSeleniumHost() {
return seleniumHost;
Expand Down Expand Up @@ -356,6 +394,12 @@ private interface ITunnelHolder {
void close(TaskListener listener);
}

@Override
public OutputStream decorateLogger(AbstractBuild build, OutputStream logger) throws IOException, InterruptedException, Run.RunnerAbortedException {
this.logParser = new SauceOnDemandLogParser(logger, build.getCharset());
return logParser;
}

private static final class TunnelHolder implements ITunnelHolder, Serializable {
private String username;

Expand All @@ -365,7 +409,7 @@ public TunnelHolder(String username) {

public void close(TaskListener listener) {
try {
JenkinsSauceManagerFactory.getInstance().createSauceConnectManager().closeTunnelsForPlan(username, listener.getLogger());
HudsonSauceManagerFactory.getInstance().createSauceConnectManager().closeTunnelsForPlan(username, listener.getLogger());
} catch (ComponentLookupException e) {
//shouldn't happen
logger.log(Level.SEVERE, "Unable to close tunnel", e);
Expand Down Expand Up @@ -417,7 +461,7 @@ public ITunnelHolder call() throws IOException {
TunnelHolder tunnelHolder = new TunnelHolder(username);
SauceTunnelManager sauceManager = null;
try {
sauceManager = JenkinsSauceManagerFactory.getInstance().createSauceConnectManager();
sauceManager = HudsonSauceManagerFactory.getInstance().createSauceConnectManager();
Process process = sauceManager.openConnection(username, key, port, sauceConnectJar, listener.getLogger());
return tunnelHolder;
} catch (ComponentLookupException e) {
Expand Down Expand Up @@ -447,4 +491,36 @@ public List<Browser> getBrowsers() {
}


/**
* @author Ross Rowe
*/
public class SauceOnDemandLogParser extends LineTransformationOutputStream {

private OutputStream outputStream;
private Charset charset;
private List<String> lines;

public SauceOnDemandLogParser(OutputStream outputStream, Charset charset) {
this.outputStream = outputStream;
this.charset = charset;
this.lines = new ArrayList<String>();
}

@Override
protected void eol(byte[] b, int len) throws IOException {

this.outputStream.write(b, 0, len);
lines.add(charset.decode(ByteBuffer.wrap(b, 0, len)).toString());
}

@Override
public void close() throws IOException {
super.close();
this.outputStream.close();
}

public List<String> getLines() {
return lines;
}
}
}
Expand Up @@ -35,10 +35,7 @@
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.TimeZone;
import java.util.*;

/**
* Show videos for the tests.
Expand All @@ -50,17 +47,17 @@ public class SauceOnDemandReport extends TestAction {
/**
* Session IDs.
*/
private final List<String> ids;
private final List<String[]> sessionIds;

/**
* Could we match session IDs to test names ?
*/
private final boolean matchingJobNames;
private static final String HMAC_KEY = "HMACMD5";

public SauceOnDemandReport(CaseResult parent, List<String> ids, boolean matchingJobNames) {
public SauceOnDemandReport(CaseResult parent, List<String[]> ids, boolean matchingJobNames) {
this.parent = parent;
this.ids = ids;
this.sessionIds = ids;
this.matchingJobNames = matchingJobNames;
}

Expand All @@ -73,11 +70,15 @@ public boolean isMatchingJobNames() {
}

public List<String> getIDs() {
List<String> ids = new ArrayList<String>();
for (String[] sessionId : sessionIds) {
ids.add(sessionId[0]);
}
return Collections.unmodifiableList(ids);
}

public String getId() {
return ids.get(0);
return getIDs().get(0);
}

public String getAuth() throws IOException {
Expand Down
Expand Up @@ -49,6 +49,7 @@ public class SauceOnDemandReportFactory extends Data {

/**
* Makes this a singleton -- since it's stateless, there's no need to keep one around for every build.
*
* @return
*/
public Object readResolve() {
Expand All @@ -61,11 +62,13 @@ public List<SauceOnDemandReport> getTestAction(TestObject testObject) {
if (testObject instanceof CaseResult) {
CaseResult cr = (CaseResult) testObject;
String jobName = cr.getFullName();
List<String> ids = findSessionIDs(jobName, cr.getStdout(), cr.getStderr());
List<String[]> ids = findSessionIDs(jobName, cr.getStdout(), cr.getStderr());
if (ids.isEmpty()) {
//try parse the build output
try {
ids = SauceOnDemandReportFactory.findSessionIDs(jobName, IOUtils.readLines(testObject.getOwner().getLogReader()));
List<String> lines = IOUtils.readLines(testObject.getOwner().getLogReader());
String[] lineArray = lines.toArray(new String[lines.size()]);
ids = SauceOnDemandReportFactory.findSessionIDs(jobName, lineArray);
} catch (IOException e) {
logger.error("Error reading log file", e);
}
Expand All @@ -77,7 +80,7 @@ public List<SauceOnDemandReport> getTestAction(TestObject testObject) {
matchingJobNames = false;
}
if (!ids.isEmpty()) {
return Collections.singletonList(new SauceOnDemandReport(cr,ids, matchingJobNames));
return Collections.singletonList(new SauceOnDemandReport(cr, ids, matchingJobNames));
}
}
return Collections.emptyList();
Expand All @@ -87,17 +90,19 @@ public List<SauceOnDemandReport> getTestAction(TestObject testObject) {
* Returns all sessions matching a given jobName in the provided logs.
* If no session is found for the jobName, return all session that do not provide job-name (old format)
*/
static List<String> findSessionIDs(String jobName, String... output) {
List<String> sessions = new ArrayList<String>();
static List<String[]> findSessionIDs(String jobName, String... output) {
List<String[]> sessions = new ArrayList<String[]>();
Pattern p = jobName != null ? SESSION_ID_PATTERN : OLD_SESSION_ID_PATTERN;
for (String text : output) {
if (text==null) continue;
if (text == null) continue;
Matcher m = p.matcher(text);
while (m.find()) {
String sessionId = m.group(1);
if (jobName == null || jobName.equals(m.group(2))) {
sessions.add(sessionId);
String job = "";
if (m.groupCount() == 2 && (jobName == null || jobName.equals(m.group(2)))) {
job = m.group(2);
}
sessions.add(new String[]{sessionId, job});
}
}
return sessions;
Expand All @@ -106,19 +111,5 @@ static List<String> findSessionIDs(String jobName, String... output) {
private static final Pattern SESSION_ID_PATTERN = Pattern.compile("SauceOnDemandSessionID=([0-9a-fA-F]+) job-name=(.*)");
private static final Pattern OLD_SESSION_ID_PATTERN = Pattern.compile("SauceOnDemandSessionID=([0-9a-fA-F]+)");

public static List<String> findSessionIDs(String jobName, List<String> lines) {
List<String> sessions = new ArrayList<String>();
Pattern p = jobName != null ? SESSION_ID_PATTERN : OLD_SESSION_ID_PATTERN;
for (String text : lines) {
if (text==null) continue;
Matcher m = p.matcher(text);
while (m.find()) {
String sessionId = m.group(1);
if (jobName == null || jobName.equals(m.group(2))) {
sessions.add(sessionId);
}
}
}
return sessions;
}

}

0 comments on commit 7f9c95a

Please sign in to comment.