Skip to content
Permalink
Browse files

Merge pull request #1634 from jglick/SimpleBuildWrapper-JENKINS-27392

[JENKINS-27392] SimpleBuildWrapper.createLoggerDecorator
  • Loading branch information
jglick committed Mar 31, 2015
2 parents 2020bbd + 804588a commit 61b3fb9483f03bcb614ee43835c21cf39afcdf23
@@ -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;
@@ -177,34 +178,51 @@ protected boolean runPreCheckout() {
}
}

/**
* 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.
@@ -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;
@@ -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();

@@ -162,7 +174,44 @@
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.
You can’t perform that action at this time.