Skip to content

Commit

Permalink
Merge pull request #1634 from jglick/SimpleBuildWrapper-JENKINS-27392
Browse files Browse the repository at this point in the history
[JENKINS-27392] SimpleBuildWrapper.createLoggerDecorator
  • Loading branch information
jglick committed Mar 31, 2015
2 parents 2020bbd + 804588a commit 61b3fb9
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 13 deletions.
44 changes: 31 additions & 13 deletions core/src/main/java/jenkins/tasks/SimpleBuildWrapper.java
Expand Up @@ -28,6 +28,7 @@
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.console.ConsoleLogFilter;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
Expand Down Expand Up @@ -177,34 +178,51 @@ private class EnvironmentWrapper extends Environment {
}
}

/**
* Allows this wrapper to decorate log output.
* @param build as is passed to {@link #setUp(Context, Run, FilePath, Launcher, TaskListener, EnvVars)}
* @return a filter which ignores its {@code build} parameter and is {@link Serializable}; or null (the default)
* @since 1.608
*/
public @CheckForNull ConsoleLogFilter createLoggerDecorator(@Nonnull Run<?,?> build) {
return null;
}

@Override public final OutputStream decorateLogger(AbstractBuild build, OutputStream logger) throws IOException, InterruptedException, Run.RunnerAbortedException {
// Doubtful this can be supported.
// Decorating a TaskListener would be more reasonable.
// But for an AbstractBuild this is called early in Run.execute, before setUp.
// And for other kinds of builds, it is unclear what this would even mean.
return logger;
ConsoleLogFilter filter = createLoggerDecorator(build);
return filter != null ? filter.decorateLogger(build, logger) : logger;
}

@Override public final Launcher decorateLauncher(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException, Run.RunnerAbortedException {
/**
* May be overridden but this will only take effect when used as a {@link BuildWrapper} on an {@link AbstractProject}.
* <p>{@inheritDoc}
* @since 1.608
*/
@Override public Launcher decorateLauncher(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException, Run.RunnerAbortedException {
return super.decorateLauncher(build, launcher, listener);
// TODO reasonable to decorate Launcher within a dynamic scope, but this signature does not mix well with Context pattern.
// Called from AbstractBuildExecution.createLauncher; how do we track what is decorating what?
// Would have to keep something like a LaunchedDecorator, not an actual Launcher, in Context.
// And createLauncher is called before even preCheckout, so much too early for the Context to have been prepared.
// Could perhaps create a proxy Launcher whose launch method checks some field in the Context remembered for the build.
return launcher;
}

/**
* May not do anything.
* {@inheritDoc}
* May be overridden but this will only take effect when used as a {@link BuildWrapper} on an {@link AbstractProject}.
* <p>{@inheritDoc}
* @since 1.608
*/
@Override public final void makeBuildVariables(AbstractBuild build, Map<String,String> variables) {}
@Override public void makeBuildVariables(AbstractBuild build, Map<String,String> variables) {
super.makeBuildVariables(build, variables);
}

/**
* May not do anything.
* {@inheritDoc}
* May be overridden but this will only take effect when used as a {@link BuildWrapper} on an {@link AbstractProject}.
* <p>{@inheritDoc}
* @since 1.608
*/
@Override public final void makeSensitiveBuildVariables(AbstractBuild build, Set<String> sensitiveVariables) {
@Override public void makeSensitiveBuildVariables(AbstractBuild build, Set<String> sensitiveVariables) {
super.makeSensitiveBuildVariables(build, sensitiveVariables);
// TODO determine if there is a meaningful way to generalize this; perhaps as a new [Run]Action recording sensitiveVariables?
// Complicated by the fact that in principle someone could call getSensitiveBuildVariables *before* the wrapper starts and actually sets those variables,
// though in practice the likely use cases would come later, and perhaps it is acceptable to omit the names of variables which are yet to be set.
Expand Down
49 changes: 49 additions & 0 deletions test/src/test/java/jenkins/tasks/SimpleBuildWrapperTest.java
Expand Up @@ -28,7 +28,11 @@
import hudson.FilePath;
import hudson.Functions;
import hudson.Launcher;
import hudson.console.ConsoleLogFilter;
import hudson.console.LineTransformationOutputStream;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.model.FreeStyleBuild;
Expand All @@ -45,18 +49,26 @@
import hudson.tasks.Shell;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.Locale;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Assume;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.CaptureEnvironmentBuilder;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestBuilder;
import org.jvnet.hudson.test.TestExtension;

public class SimpleBuildWrapperTest {

@ClassRule public static BuildWatcher buildWatcher = new BuildWatcher();
@Rule public JenkinsRule r = new JenkinsRule();
@Rule public TemporaryFolder tmp = new TemporaryFolder();

Expand Down Expand Up @@ -162,7 +174,44 @@ private static final class DisposerImpl extends Disposer {
return true;
}
}
}

@Issue("JENKINS-27392")
@Test public void loggerDecorator() throws Exception {
FreeStyleProject p = r.createFreeStyleProject();
p.getBuildWrappersList().add(new WrapperWithLogger());
p.getBuildersList().add(new TestBuilder() {
@Override public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
listener.getLogger().println("sending a message");
return true;
}
});
r.assertLogContains("SENDING A MESSAGE", r.buildAndAssertSuccess(p));
}
public static class WrapperWithLogger extends SimpleBuildWrapper {
@Override public void setUp(Context context, Run<?,?> build, FilePath workspace, Launcher launcher, TaskListener listener, EnvVars initialEnvironment) throws IOException, InterruptedException {}
@Override public ConsoleLogFilter createLoggerDecorator(Run<?,?> build) {
return new UpcaseFilter();
}
private static class UpcaseFilter extends ConsoleLogFilter implements Serializable {
private static final long serialVersionUID = 1;
@SuppressWarnings("rawtypes") // inherited
@Override public OutputStream decorateLogger(AbstractBuild _ignore, final OutputStream logger) throws IOException, InterruptedException {
return new LineTransformationOutputStream() {
@Override protected void eol(byte[] b, int len) throws IOException {
logger.write(new String(b, 0, len).toUpperCase(Locale.ROOT).getBytes());
}
};
}
}
@TestExtension("loggerDecorator") public static class DescriptorImpl extends BuildWrapperDescriptor {
@Override public String getDisplayName() {
return "WrapperWithLogger";
}
@Override public boolean isApplicable(AbstractProject<?,?> item) {
return true;
}
}
}

}

0 comments on commit 61b3fb9

Please sign in to comment.