Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timestamp skip: optional feature to skip unmodified source files to speed up builds #11

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
*
*/
package com.googlecode.jslint4java.maven;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.codehaus.plexus.util.FileUtils;


/**
* Produce a list of files to be excluded if they haven't been modified
* @author danigiri
* @see TimestampFormatter
*/
public class FileTimestampExcludeLister {

private List<File> files;
private File reportFile;

public FileTimestampExcludeLister(List<File> files, File reportFile) {
this.files = files;
this.reportFile = reportFile;
}

/**
* @return list of excluded files
* @throws IOException if report file can't be read
*////////////////////////////////////////////////////////////////////////////////
public List<File> files() throws IOException {

List<File> excludedFiles = new ArrayList<File>();

String timestampsReport = FileUtils.fileRead(reportFile);
String[] timestampLines = timestampsReport.split("\n");
for (int i=0;i<timestampLines.length;i++) {
String[] resultLineSplit = timestampLines[i].trim().split(" ");
File sourceFile = new File(resultLineSplit[0]);
long timestamp = Long.parseLong(resultLineSplit[1]);
if (sourceFile.exists() && sourceFile.lastModified()==timestamp) {
excludedFiles.add(sourceFile);
}
}
return excludedFiles;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
*/
// TODO Support alternate jslint
// TODO Support HTML reports (site plugin mojo?)
// TODO make sure files with issues are processed all the time when skipping untouched files
public class JSLintMojo extends AbstractMojo {

/** Where to write the HTML report. */
Expand All @@ -59,6 +60,14 @@ public class JSLintMojo extends AbstractMojo {
/** Where to write the junit report. */
private static final String JUNIT_XML = "junit.xml";

/** Where to find the timestamps */
private static final String REPORT_TIMESTAMPS = "timestamps.txt";

/**
* @parameter default-value="${basedir}
*/
private File baseDirectory;

/**
* Specifies the the source files to be excluded for JSLint (relative to
* {@link #defaultSourceFolder}). Maven applies its own defaults.
Expand Down Expand Up @@ -140,6 +149,15 @@ public class JSLintMojo extends AbstractMojo {
*/
private long timeout;

/**
* Generate a timestamp file so unmodified files aren't checked on subsequent maven runs.
* Unmodified files will be skipped and will <b>not</b> show up on report files.
* (This includes files with issues that are left untouched.
*
* @parameter expression="${jslint.checkFileModificationTimes}" default-value="false"
*/
private boolean checkFileModificationTimes = false;

/** Add a single option. For testing only. */
void addOption(Option sloppy, String value) {
options.put(sloppy.name().toLowerCase(Locale.ENGLISH), value);
Expand Down Expand Up @@ -178,7 +196,11 @@ public void execute() throws MojoExecutionException, MojoFailureException {
JSLint jsLint = applyJSlintSource();
applyDefaults();
applyOptions(jsLint);
List<File> files = getFilesToProcess();
List<File> allFiles = getAllSourceFiles();
List<File> files = filesAfterRemovingNonModifiedFrom(allFiles);
if (checkFileModificationTimes && files.size()>0) {
generateTimestampReport(allFiles);
}
int failures = 0;
ReportWriter reporter = makeReportWriter();
try {
Expand All @@ -192,7 +214,7 @@ public void execute() throws MojoExecutionException, MojoFailureException {
reporter.close();
}
if (failures > 0) {
String message = "JSLint found " + failures + " problems in " + files.size() + " files";
String message = "JSLint found " + failures + " problems in " + allFiles.size() + " files";
if (failOnError) {
throw new MojoFailureException(message);
} else {
Expand All @@ -201,7 +223,7 @@ public void execute() throws MojoExecutionException, MojoFailureException {
}
}

private JSLint applyJSlintSource() throws MojoExecutionException {
private JSLint applyJSlintSource() throws MojoExecutionException {
JSLintBuilder builder = new JSLintBuilder();
if (timeout > 0) {
builder.timeout(timeout);
Expand All @@ -228,11 +250,11 @@ String getEncoding() {
*
* @return a {@link List} of {@link File}s.
*/
private List<File> getFilesToProcess() throws MojoExecutionException {
private List<File> getAllSourceFiles() throws MojoExecutionException {
// Defaults.
getLog().debug("includes=" + includes);
getLog().debug("excludes=" + excludes);

List<File> files = new ArrayList<File>();
for (File folder : sourceFolders) {
getLog().debug("searching " + folder);
Expand All @@ -243,11 +265,53 @@ private List<File> getFilesToProcess() throws MojoExecutionException {
throw new MojoExecutionException("Error listing files", e);
}
}

return files;
}

// Visible for testing only.
/** Return files to be excluded as they haven't been modified according to timestamp report
* @param files to be checked
* @return a {@link List} of {@link File}s to be excluded
* @throws MojoExecutionException if timestamp file can't be read
*/
private List<File> filesNotModifiedAccordingToTimestampReport(List<File> files) throws MojoExecutionException {
List<File> notModifiedFiles = new ArrayList<File>();
File timestampsFile = new File(outputFolder, REPORT_TIMESTAMPS);
if (timestampsFile.exists()) {
FileTimestampExcludeLister excludeLister = new FileTimestampExcludeLister(files,timestampsFile);
try {
notModifiedFiles = excludeLister.files();
} catch (IOException e) {
throw new MojoExecutionException("Couldn't read timestamp exclude file",e);
}
}
return notModifiedFiles;
}

/** if we have set the option not to process untouched files we remove them from the list
* according to the timestamp report
*/
private List<File> filesAfterRemovingNonModifiedFrom(List<File> allFiles) throws MojoExecutionException {
List<File> fileList = new ArrayList<File>();
fileList.addAll(allFiles);
if (checkFileModificationTimes) {
List<File> unmodifiedFiles = filesNotModifiedAccordingToTimestampReport(allFiles);
if (unmodifiedFiles.size()>0) {
if (baseDirectory!=null) { // it's only null in testing
List<String> unmodifiedFileRelativePaths = new ArrayList<String>();
String baseDirectoryPath = baseDirectory.getPath()+"/";
for (File f : unmodifiedFiles) {
unmodifiedFileRelativePaths.add(f.getPath().replaceFirst(baseDirectoryPath, ""));
}
getLog().warn("Excluding non-modified files="+unmodifiedFileRelativePaths);
}
fileList.removeAll(unmodifiedFiles);
}
}
return fileList;
}

// Visible for testing only.
Map<String, String> getOptions() {
return options;
}
Expand Down Expand Up @@ -292,7 +356,21 @@ private void logIssuesToConsole(JSLintResult result) {
}
}

private ReportWriter makeReportWriter() {
private void generateTimestampReport(List<File> files) {
ReportWriterImpl reporter = new ReportWriterImpl(new File(outputFolder, REPORT_TIMESTAMPS),
new TimestampFormatter());
try {
reporter.open();
for (File file : files) {
JSLintResult result = new JSLintResult.ResultBuilder(file.getPath()).build();
reporter.report(result);
}
} finally {
reporter.close();
}
}

private ReportWriter makeReportWriter() {
ReportWriterImpl f1 = new ReportWriterImpl(new File(outputFolder, JSLINT_XML),
new JSLintXmlFormatter());
ReportWriterImpl f2 = new ReportWriterImpl(new File(outputFolder, JUNIT_XML),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,5 @@ public void report(JSLintResult result) {
throw new RuntimeException(e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

package com.googlecode.jslint4java.maven;

import java.io.File;

import com.googlecode.jslint4java.Issue;
import com.googlecode.jslint4java.JSLintResult;
import com.googlecode.jslint4java.formatter.JSLintResultFormatter;


/**
* Output filename paths and last modification date
* @author danigiri
*/
public class TimestampFormatter implements JSLintResultFormatter {


/** No footer required. */
public String footer() {
return null;
}

public String format(JSLintResult result) {
String nl = System.getProperty("line.separator");
StringBuilder sb = new StringBuilder();
String fileName = result.getName();
sb.append(fileName);
sb.append(" ");
sb.append(new File(fileName).lastModified());
sb.append(nl);
return sb.toString();
}

/** No footer required. */
public String header() {
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
*
*/
package com.googlecode.jslint4java.maven;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.junit.matchers.JUnitMatchers.*;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;

import org.codehaus.plexus.util.FileUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import com.googlecode.jslint4java.JSLintResult;


/**
* @author danigiri
*/
public class FileTimestampExcludeListerTest {

@Rule
public TemporaryFolder tmpf = new TemporaryFolder();

private File sourceCopyDirectory;
private File reportFile;
private List<File> sourceFiles;

private void generateReportFrom(List<File> files,File reportFile) {

ReportWriterImpl reportWriter = new ReportWriterImpl(reportFile, new TimestampFormatter());
try{
reportWriter.open();
for (File f : files) {
JSLintResult result = new JSLintResult.ResultBuilder(f.getPath()).build();
reportWriter.report(result);
}
} catch (Exception e) {
fail("This report should work"+e.getMessage());
} finally {
reportWriter.close();
}
assertTrue("Report exists (paranoid)",reportFile.exists());

}


private List<File> getExcludedFilesFromReport() throws IOException {

FileTimestampExcludeLister excludeLister = new FileTimestampExcludeLister(sourceFiles,reportFile);
List<File> excludedFiles = excludeLister.files();
return excludedFiles;

}


@Before
public void setUpSourceDirectory() throws Exception {

try {
File sourceDirectory = new File(FileTimestampExcludeListerTest.class.getResource("good-js").toURI());
// Not that I'm paranoid…
assertTrue("source exists", sourceDirectory.exists());
assertTrue("source is a directory", sourceDirectory.isDirectory());
sourceCopyDirectory = tmpf.newFolder();
FileUtils.copyDirectory(sourceDirectory, sourceCopyDirectory);
File reportDirectory = tmpf.newFolder();
reportFile = new File(reportDirectory,"timestamps.txt");
sourceFiles = FileUtils.getFiles(sourceCopyDirectory, "*.js", "*.txt");
generateReportFrom(sourceFiles, reportFile);

} catch (URISyntaxException e) {
throw new RuntimeException(e);
}

}


@Test
public void testExcludeAll() throws Exception {

// should exclude all
List<File> excludedFiles = getExcludedFilesFromReport();
assertThat("excluded count",excludedFiles.size(),is(sourceFiles.size()));

}


@Test
public void testExcludeNone() throws Exception {

Thread.sleep(1001); // wait one second so timestamp actually changes

for (File file : sourceFiles) {
FileUtils.fileAppend(file.getPath(), " ");
}

List<File> excludedFiles = getExcludedFilesFromReport();
assertThat("excluded count", excludedFiles.size(), is(0));

}


@Test
public void testExcludeOne() throws Exception {

Thread.sleep(1001); // wait one second so timestamp actually changes

File modifiedFile = sourceFiles.get(0);
FileUtils.fileAppend(modifiedFile.getPath(), " ");
List<File> excludedFiles = getExcludedFilesFromReport();
assertEquals("excluded count", 1, excludedFiles.size());
assertThat("not exclude modified", excludedFiles, not(hasItem(modifiedFile)));

}


@Test(expected=IOException.class)
public void testReportFileDoesNotExist() throws IOException {

FileTimestampExcludeLister excludeLister = new FileTimestampExcludeLister(sourceFiles,new File("doesntexist"));
excludeLister.files();
fail("exception is not thrown");
}

}
Loading