diff --git a/build.gradle b/build.gradle index 777bc7c..ae53221 100644 --- a/build.gradle +++ b/build.gradle @@ -1,19 +1,23 @@ plugins { - id 'org.jenkins-ci.jpi' version '0.17.0' - id 'com.github.hierynomus.license' version '0.12.1' + id 'org.jenkins-ci.jpi' version '0.32.0' + id 'com.github.hierynomus.license' version '0.12.1' } group = 'org.jenkins-ci.plugins' -version = '1.9-SNAPSHOT' +version = '1.10-SNAPSHOT' description = 'Provides an easy way to execute and report Clang scan-build errors using jenkins' license { header = file('LICENSE.mit') } +repositories { + mavenCentral() +} + jenkinsPlugin { // version of Jenkins core this plugin depends on - coreVersion = '1.609.3' + coreVersion = '2.164.1' shortName = 'clang-scanbuild' displayName = 'Clang Scan-Build Plugin' @@ -37,7 +41,13 @@ jenkinsPlugin { } dependencies { - jenkinsTest 'org.jenkins-ci.plugins:matrix-project:1.6@jar' + testCompile group: 'org.easymock', name: 'easymock', version: '4.0.2' + compile group: 'org.jenkins-ci.plugins.workflow', name: 'workflow-step-api', version: '2.19' + +// compile group: 'org.jenkins-ci.plugins', name: 'matrix-project', version: '1.14' + jenkinsPlugins group: 'org.jenkins-ci.plugins.workflow', name: 'workflow-cps', version: '2.68' + jenkinsPlugins group: 'org.jenkins-ci.plugins.workflow', name: 'workflow-job', version: '2.32' + jenkinsPlugins group: 'org.jenkins-ci.plugins.workflow', name: 'workflow-step-api', version: '2.19' } defaultTasks 'jpi', 'test' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index af10c6e..53543e0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon May 09 19:05:02 PDT 2016 +#Mon May 27 16:43:52 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.3-all.zip diff --git a/src/main/java/jenkins/plugins/clangscanbuild/ClangScanBuildUtils.java b/src/main/java/jenkins/plugins/clangscanbuild/ClangScanBuildUtils.java index 9bfff33..1467aa7 100644 --- a/src/main/java/jenkins/plugins/clangscanbuild/ClangScanBuildUtils.java +++ b/src/main/java/jenkins/plugins/clangscanbuild/ClangScanBuildUtils.java @@ -22,7 +22,7 @@ package jenkins.plugins.clangscanbuild; import hudson.FilePath; -import hudson.model.AbstractBuild; +import hudson.model.Run; public class ClangScanBuildUtils{ @@ -37,7 +37,7 @@ public static String getTransparentImagePath(){ return "/plugin/" + SHORTNAME + "/transparent.png"; } - public static FilePath locateClangScanBuildReportFolder( AbstractBuild build, String folderName ){ + public static FilePath locateClangScanBuildReportFolder(Run build, String folderName ){ if( build == null ) return null; return new FilePath( new FilePath( build.getRootDir() ), folderName ); } diff --git a/src/main/java/jenkins/plugins/clangscanbuild/actions/ClangScanBuildAction.java b/src/main/java/jenkins/plugins/clangscanbuild/actions/ClangScanBuildAction.java index ace9cff..1e13be7 100644 --- a/src/main/java/jenkins/plugins/clangscanbuild/actions/ClangScanBuildAction.java +++ b/src/main/java/jenkins/plugins/clangscanbuild/actions/ClangScanBuildAction.java @@ -27,15 +27,19 @@ import hudson.model.AbstractBuild; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.regex.Pattern; import java.util.logging.Logger; -import static java.util.logging.Level.WARNING; + import static java.util.logging.Level.FINEST; +import hudson.model.Run; import jenkins.plugins.clangscanbuild.ClangScanBuildUtils; import jenkins.plugins.clangscanbuild.history.ClangScanBuildBugSummary; -import org.kohsuke.stapler.QueryParameter; +import jenkins.tasks.SimpleBuildStep; import org.kohsuke.stapler.StaplerProxy; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; @@ -47,7 +51,7 @@ * * @author Josh Kennedy */ -public class ClangScanBuildAction implements Action, StaplerProxy, ModelObject{ +public class ClangScanBuildAction implements Action, SimpleBuildStep.LastBuildAction, StaplerProxy, ModelObject{ private static final Logger LOGGER = Logger.getLogger(ClangScanBuildAction.class.getName()); public static final String BUILD_ACTION_URL_NAME = "clangScanBuildBugs"; @@ -56,64 +60,25 @@ public class ClangScanBuildAction implements Action, StaplerProxy, ModelObject{ private boolean markBuildUnstable; private int bugCount; private String outputFolderName; - + public Run build; + private final List projectActions; + private Pattern APPROVED_REPORT_REQUEST_PATTERN = Pattern.compile( "[^.\\\\/]*\\.html|StaticAnalyzer.*\\.html" ); - public ClangScanBuildAction( AbstractBuild build, int bugCount, boolean markBuildUnstable, - int bugThreshold, FilePath bugSummaryXML, String outputFolderName ){ + public ClangScanBuildAction(Run build, int bugCount, boolean markBuildUnstable, + int bugThreshold, FilePath bugSummaryXML, String outputFolderName ){ this.bugThreshold = bugThreshold; this.bugCount = bugCount; this.bugSummaryXML = bugSummaryXML; this.markBuildUnstable = markBuildUnstable; this.build = build; this.outputFolderName = outputFolderName; - } - public AbstractBuild build; - - public boolean buildFailedDueToExceededThreshold(){ - if( !markBuildUnstable ) return false; - return getBugCount() > bugThreshold; - } - - public int getBugThreshhold(){ - return bugThreshold; + List projectActions = new ArrayList<>(); + projectActions.add(new ClangScanBuildProjectAction(build.getParent())); + this.projectActions = projectActions; } - - /** - * The only thing stored in the actual builds in the bugCount and bugThreshold. This was done in order to make the - * build XML smaller to reduce load times. The counts are need in order to render the trend charts. - * - * This method actually loads the XML file that was generated at build time and placed alongside the clang output files - * This XML contains the list of bugs and is used to render the report which links to the clang files. - * - * DON'T CALL THIS UNLESS YOU NEED THE ACTUAL BUG SUMMARY - */ - public ClangScanBuildBugSummary loadBugSummary(){ - if( bugSummaryXML == null ) return null; - - try{ - if( bugSummaryXML.length() != 0 ) - { - return (ClangScanBuildBugSummary) AbstractBuild.XSTREAM.fromXML( bugSummaryXML.read() ); - } - else - { - return null; - } - }catch( java.lang.InterruptedException ie ){ - LOGGER.log(FINEST, "", ie); - return null; - }catch( IOException ioe ){ - LOGGER.log(FINEST, "", ioe); - return null; - } - } - - public int getBugCount(){ - return bugCount; - } - + /** * Indicates which icon should be displayed next to the link */ @@ -149,7 +114,59 @@ public String getUrlName() { public Object getTarget(){ return this; } - + + @Override + public Collection getProjectActions() { + return this.projectActions; + } + + public Run getBuild() { + return build; + } + + public int getBugCount(){ + return bugCount; + } + + public boolean buildFailedDueToExceededThreshold(){ + if( !markBuildUnstable ) return false; + return getBugCount() > bugThreshold; + } + + public int getBugThreshhold(){ + return bugThreshold; + } + + /** + * The only thing stored in the actual builds in the bugCount and bugThreshold. This was done in order to make the + * build XML smaller to reduce load times. The counts are need in order to render the trend charts. + * + * This method actually loads the XML file that was generated at build time and placed alongside the clang output files + * This XML contains the list of bugs and is used to render the report which links to the clang files. + * + * DON'T CALL THIS UNLESS YOU NEED THE ACTUAL BUG SUMMARY + */ + public ClangScanBuildBugSummary loadBugSummary(){ + if( bugSummaryXML == null ) return null; + + try{ + if( bugSummaryXML.length() != 0 ) + { + return (ClangScanBuildBugSummary) AbstractBuild.XSTREAM.fromXML( bugSummaryXML.read() ); + } + else + { + return null; + } + }catch( java.lang.InterruptedException ie ){ + LOGGER.log(FINEST, "", ie); + return null; + }catch( IOException ioe ){ + LOGGER.log(FINEST, "", ioe); + return null; + } + } + /** * This method is used to serve up report HTML files from the hidden build folder. It essentially exposes * the reports to the web. @@ -187,5 +204,4 @@ private String trimFirstSlash( String path ){ if( !path.startsWith("/") ) return path.trim(); return path.substring(1).trim(); } - } diff --git a/src/main/java/jenkins/plugins/clangscanbuild/actions/ClangScanBuildProjectAction.java b/src/main/java/jenkins/plugins/clangscanbuild/actions/ClangScanBuildProjectAction.java index d844c27..1119aea 100644 --- a/src/main/java/jenkins/plugins/clangscanbuild/actions/ClangScanBuildProjectAction.java +++ b/src/main/java/jenkins/plugins/clangscanbuild/actions/ClangScanBuildProjectAction.java @@ -23,6 +23,7 @@ import hudson.model.Action; import hudson.model.AbstractProject; +import hudson.model.Job; import hudson.util.ChartUtil; import java.io.IOException; @@ -47,11 +48,11 @@ public class ClangScanBuildProjectAction implements Action{ private static final String DEFAULT_IMAGE = "/images/headless.png"; - public final AbstractProject project; private ClangScanBuildHistoryGatherer gatherer = new ClangScanBuildHistoryGathererImpl(); - - public ClangScanBuildProjectAction( AbstractProject project ) { - super(); + + public final Job project; + + public ClangScanBuildProjectAction(Job project ) { this.project = project; } @@ -59,13 +60,6 @@ public ClangScanBuildProjectAction( AbstractProject project ) { public String getIconFileName() { return ClangScanBuildUtils.getIconsPath() + "scanbuild-32x32.png"; } - - /** - * Doing this wastefully because i do not know the lifecycle of this object. Is it a singleton? - */ - public ClangBuildGraph getGraph(){ - return new ClangBuildGraph( gatherer.gatherHistoryDataSet( project.getLastBuild() ) ); - } @Override public String getDisplayName() { @@ -77,6 +71,17 @@ public String getUrlName() { return "clangScanBuildTrend"; } + public Job getProject() { + return project; + } + + /** + * Doing this wastefully because i do not know the lifecycle of this object. Is it a singleton? + */ + public ClangBuildGraph getGraph(){ + return new ClangBuildGraph( gatherer.gatherHistoryDataSet( project.getLastBuild() ) ); + } + public void doGraph( StaplerRequest req, StaplerResponse rsp ) throws IOException { if( ChartUtil.awtProblemCause != null ){ rsp.sendRedirect2( req.getContextPath() + DEFAULT_IMAGE ); @@ -90,9 +95,8 @@ public void doMap( StaplerRequest req, StaplerResponse rsp ) throws IOException getGraph().doMap( req, rsp ); } - public boolean buildDataExists(){ - List points = gatherer.gatherHistoryDataSet( project.getLastBuild() ); - return points.size() > 0; - } - + public boolean buildDataExists() { + List points = gatherer.gatherHistoryDataSet(project.getLastBuild()); + return points.size() > 0; + } } diff --git a/src/main/java/jenkins/plugins/clangscanbuild/history/ClangScanBuildHistoryGatherer.java b/src/main/java/jenkins/plugins/clangscanbuild/history/ClangScanBuildHistoryGatherer.java index 468fe10..069ce62 100644 --- a/src/main/java/jenkins/plugins/clangscanbuild/history/ClangScanBuildHistoryGatherer.java +++ b/src/main/java/jenkins/plugins/clangscanbuild/history/ClangScanBuildHistoryGatherer.java @@ -21,14 +21,13 @@ */ package jenkins.plugins.clangscanbuild.history; -import hudson.model.AbstractBuild; - import java.util.List; +import hudson.model.Run; import jenkins.plugins.clangscanbuild.reports.GraphPoint; public interface ClangScanBuildHistoryGatherer { - public List gatherHistoryDataSet( AbstractBuild latestBuild ); + public List gatherHistoryDataSet(Run latestBuild ); } diff --git a/src/main/java/jenkins/plugins/clangscanbuild/history/ClangScanBuildHistoryGathererImpl.java b/src/main/java/jenkins/plugins/clangscanbuild/history/ClangScanBuildHistoryGathererImpl.java index caea991..7411e0a 100644 --- a/src/main/java/jenkins/plugins/clangscanbuild/history/ClangScanBuildHistoryGathererImpl.java +++ b/src/main/java/jenkins/plugins/clangscanbuild/history/ClangScanBuildHistoryGathererImpl.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.List; +import hudson.model.Run; import jenkins.plugins.clangscanbuild.actions.ClangScanBuildAction; import jenkins.plugins.clangscanbuild.reports.GraphPoint; @@ -42,12 +43,12 @@ public ClangScanBuildHistoryGathererImpl( int numberOfBuildsToGather ){ this.numberOfBuildsToGather = numberOfBuildsToGather; } - public List gatherHistoryDataSet( AbstractBuild latestBuild ){ + public List gatherHistoryDataSet(Run latestBuild ){ List points = new ArrayList(); if( latestBuild == null ) return points; int gatheredBuilds = 0; - for( AbstractBuild build = latestBuild; build != null; build = build.getPreviousBuild() ){ + for( Run build = latestBuild; build != null; build = build.getPreviousBuild() ){ if( gatheredBuilds >= numberOfBuildsToGather ) return points; ClangScanBuildAction action = build.getAction( ClangScanBuildAction.class ); diff --git a/src/main/java/jenkins/plugins/clangscanbuild/publisher/ClangScanBuildPublisher.java b/src/main/java/jenkins/plugins/clangscanbuild/publisher/ClangScanBuildPublisher.java index b2dea9d..c4c056b 100644 --- a/src/main/java/jenkins/plugins/clangscanbuild/publisher/ClangScanBuildPublisher.java +++ b/src/main/java/jenkins/plugins/clangscanbuild/publisher/ClangScanBuildPublisher.java @@ -26,11 +26,7 @@ import hudson.FilePath; import hudson.Launcher; import hudson.Util; -import hudson.model.Action; -import hudson.model.BuildListener; -import hudson.model.Result; -import hudson.model.AbstractBuild; -import hudson.model.AbstractProject; +import hudson.model.*; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Recorder; @@ -48,8 +44,12 @@ import jenkins.plugins.clangscanbuild.actions.ClangScanBuildProjectAction; import jenkins.plugins.clangscanbuild.history.ClangScanBuildBug; import jenkins.plugins.clangscanbuild.history.ClangScanBuildBugSummary; +import jenkins.tasks.SimpleBuildStep; +import org.kohsuke.stapler.DataBoundConstructor; -public class ClangScanBuildPublisher extends Recorder{ +import javax.annotation.Nonnull; + +public class ClangScanBuildPublisher extends Recorder implements SimpleBuildStep { private static final Logger LOGGER = Logger.getLogger( ClangScanBuildPublisher.class.getName() ); @@ -71,14 +71,13 @@ public class ClangScanBuildPublisher extends Recorder{ private boolean markBuildUnstableWhenThresholdIsExceeded; + @DataBoundConstructor public ClangScanBuildPublisher( boolean markBuildUnstableWhenThresholdIsExceeded, int bugThreshold, String clangexcludedpaths, String reportFolderName ){ - - super(); this.markBuildUnstableWhenThresholdIsExceeded = markBuildUnstableWhenThresholdIsExceeded; this.bugThreshold = bugThreshold; this.clangexcludedpaths = Util.fixNull(clangexcludedpaths); @@ -93,25 +92,16 @@ public boolean isMarkBuildUnstableWhenThresholdIsExceeded(){ return markBuildUnstableWhenThresholdIsExceeded; } - public void setBugThreshold(int bugThreshold) { - this.bugThreshold = bugThreshold; - } - - public void setClangexcludedpaths(String clangExcludePaths){ - this.clangexcludedpaths = Util.fixNull(clangExcludePaths); - } - - public void setReportFolderName(String folderName){ - this.reportFolderName = Util.fixNull(folderName); - } - public String getReportFolderName(){ return reportFolderName; } - + public String getClangexcludedpaths(){ + return clangexcludedpaths; + } + @Override - public Action getProjectAction( AbstractProject project ){ + public Action getProjectAction(AbstractProject project ){ return new ClangScanBuildProjectAction( project ); } @@ -121,23 +111,25 @@ public ClangScanBuildPublisherDescriptor getDescriptor() { } public BuildStepMonitor getRequiredMonitorService() { - return BuildStepMonitor.NONE; - } - - public String getClangexcludedpaths(){ - return clangexcludedpaths; + return BuildStepMonitor.BUILD; } @Override - public boolean perform( AbstractBuild build, Launcher launcher, BuildListener listener ) throws InterruptedException, IOException { - + public void perform(@Nonnull Run build, @Nonnull FilePath workspace, @Nonnull Launcher launcher, + @Nonnull TaskListener listener) throws InterruptedException, IOException { listener.getLogger().println( "Publishing Clang scan-build results" ); // Expand build variables in the reportFolderName - EnvVars env = build.getEnvironment(listener); + EnvVars env; + if (build instanceof AbstractBuild) { + env = build.getEnvironment(listener); + env.overrideAll(((AbstractBuild) build).getBuildVariables()); + } else { + env = new EnvVars(); + } String expandedReportFolderName = env.expand(reportFolderName); - FilePath reportOutputFolder = new FilePath(build.getWorkspace(), expandedReportFolderName); + FilePath reportOutputFolder = new FilePath(workspace, expandedReportFolderName); FilePath reportMasterOutputFolder = ClangScanBuildUtils.locateClangScanBuildReportFolder(build, expandedReportFolderName); // This copies the reports out of the generate date sub folder to the root of the reports folder and then deletes the clang generated folder @@ -155,27 +147,27 @@ public boolean perform( AbstractBuild build, Launcher launcher, BuildListe // this builds and new bug summary and populates it with bugs ClangScanBuildBugSummary newBugSummary = new ClangScanBuildBugSummary( build.number ); - String[] tokens = new String[0]; - if(this.getClangexcludedpaths().length() > 0){ - tokens = this.getClangexcludedpaths().split(","); - } + String[] tokens = new String[0]; + if(getClangexcludedpaths().length() > 0){ + tokens = getClangexcludedpaths().split(","); + } - for( FilePath report : clangReports ){ - // bugs are parsed inside this method: - ClangScanBuildBug bug = createBugFromClangScanBuildHtml( build.getProject().getName(), report, previousBugSummary, build.getWorkspace().getRemote() ); - boolean validBug = true; - for(String token:tokens){ - String trimmedToken = token.trim().toLowerCase(); - if(bug.sourceFile.toLowerCase().contains(trimmedToken)){ - listener.getLogger().println( "Skipping file: " + bug.sourceFile + " because it matches exclusion pattern: " + trimmedToken ); - validBug = false; - break; + for( FilePath report : clangReports ){ + // bugs are parsed inside this method: + ClangScanBuildBug bug = createBugFromClangScanBuildHtml( build.getParent().getName(), report, previousBugSummary, workspace.getRemote() ); + boolean validBug = true; + for(String token:tokens){ + String trimmedToken = token.trim().toLowerCase(); + if(bug.sourceFile.toLowerCase().contains(trimmedToken)){ + listener.getLogger().println( "Skipping file: " + bug.sourceFile + " because it matches exclusion pattern: " + trimmedToken ); + validBug = false; + break; + } + } + if(validBug) { + newBugSummary.add( bug ); + } } - } - if(validBug) { - newBugSummary.add( bug ); - } - } // this line dumps a bugSummary.xml file to the build artifacts. did this instead of using job config xml for performance //FilePath bugSummaryXMLFile = new FilePath( reportOutputFolder, "bugSummary.xml" ); @@ -192,8 +184,6 @@ public boolean perform( AbstractBuild build, Launcher launcher, BuildListe listener.getLogger().println( "Clang scan-build threshhold exceeded." ); build.setResult( Result.UNSTABLE ); } - - return true; } private ClangScanBuildBug createBugFromClangScanBuildHtml( String projectName, FilePath report, ClangScanBuildBugSummary previousBugSummary, String workspacePath ) throws InterruptedException { @@ -207,7 +197,7 @@ private ClangScanBuildBug createBugFromClangScanBuildHtml( String projectName, F return bug; } - private ClangScanBuildBugSummary getBugSummaryForLastBuild( AbstractBuild build) { + private ClangScanBuildBugSummary getBugSummaryForLastBuild(Run build) { if( build.getPreviousBuild() != null ){ ClangScanBuildAction previousAction = build.getPreviousBuild().getAction( ClangScanBuildAction.class ); if( previousAction != null ){ @@ -230,7 +220,7 @@ private ClangScanBuildBugSummary getBugSummaryForLastBuild( AbstractBuild * This method locates the subfolders of the output folder and copies their contents * to the build archive folder. */ - private void copyClangReportsOutOfGeneratedSubFolder( FilePath reportsFolder, BuildListener listener ){ + private void copyClangReportsOutOfGeneratedSubFolder(FilePath reportsFolder, TaskListener listener ){ try{ List subFolders = reportsFolder.listDirectories(); if( subFolders.isEmpty() ){ @@ -249,7 +239,7 @@ private void copyClangReportsOutOfGeneratedSubFolder( FilePath reportsFolder, Bu /** * Copy clang output folder to have access to html files from the master */ - private void copyClangReportsToMaster( FilePath reportsFolder, FilePath materPath, BuildListener listener ){ + private void copyClangReportsToMaster(FilePath reportsFolder, FilePath materPath, TaskListener listener ){ try{ reportsFolder.copyRecursiveTo( materPath ); }catch( Exception e ){ @@ -318,11 +308,10 @@ private String getMatch( Pattern pattern, String contents ){ /** * This locates all the generated HTML bug reports from scan-build and returns them as a list. */ - protected List locateClangBugReports( FilePath clangOutputFolder ) throws IOException, InterruptedException { + private List locateClangBugReports( FilePath clangOutputFolder ) throws IOException, InterruptedException { List files = new ArrayList(); if( !clangOutputFolder.exists() ) return files; files.addAll( Arrays.asList( clangOutputFolder.list( "**/report-*.html" ) ) ); return files; } - } diff --git a/src/main/java/jenkins/plugins/clangscanbuild/publisher/ClangScanBuildPublisherDescriptor.java b/src/main/java/jenkins/plugins/clangscanbuild/publisher/ClangScanBuildPublisherDescriptor.java index 9501b31..191984f 100644 --- a/src/main/java/jenkins/plugins/clangscanbuild/publisher/ClangScanBuildPublisherDescriptor.java +++ b/src/main/java/jenkins/plugins/clangscanbuild/publisher/ClangScanBuildPublisherDescriptor.java @@ -21,16 +21,19 @@ */ package jenkins.plugins.clangscanbuild.publisher; +import hudson.Extension; import jenkins.plugins.clangscanbuild.ClangScanBuildUtils; import net.sf.json.JSONObject; +import org.jenkinsci.Symbol; import org.kohsuke.stapler.StaplerRequest; import hudson.model.AbstractProject; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Publisher; - +@Symbol("publishClangScanBuildResult") +@Extension public class ClangScanBuildPublisherDescriptor extends BuildStepDescriptor{ public ClangScanBuildPublisherDescriptor(){ diff --git a/src/test/java/jenkins/plugins/clangscanbuild/commands/ScanBuildCommandTest.java b/src/test/java/jenkins/plugins/clangscanbuild/commands/ScanBuildCommandTest.java index 0d5d27f..dd62cc3 100644 --- a/src/test/java/jenkins/plugins/clangscanbuild/commands/ScanBuildCommandTest.java +++ b/src/test/java/jenkins/plugins/clangscanbuild/commands/ScanBuildCommandTest.java @@ -90,7 +90,7 @@ private String buildCommandAndReturn( ScanBuildCommand command ) throws Exceptio context.log( (String) EasyMock.notNull() ); EasyMock.expectLastCall().anyTimes(); - Capture argumentListCapture = new Capture(); + Capture argumentListCapture = EasyMock.newCapture(); EasyMock.expect( context.waitForProcess( EasyMock.same( command.getProjectDirectory() ), EasyMock.capture( argumentListCapture ) ) ).andReturn( 0 ); EasyMock.replay( context );