├── .gitignore ├── CHANGELOG.md ├── Jenkinsfile ├── README.md ├── pom.xml └── src ├── main ├── java │ └── hudson │ │ └── plugins │ │ └── rubyMetrics │ │ ├── AbstractRailsTaskPublisher.java │ │ ├── AbstractRubyMetricsBuildAction.java │ │ ├── AbstractRubyMetricsProjectAction.java │ │ ├── AbstractRubyMetricsPublisher.java │ │ ├── HtmlParser.java │ │ ├── HtmlPublisher.java │ │ ├── Utils.java │ │ ├── flog │ │ ├── FlogBuildAction.java │ │ ├── FlogExecutor.java │ │ ├── FlogParser.java │ │ ├── FlogProjectAction.java │ │ ├── FlogPublisher.java │ │ └── model │ │ │ ├── FlogBuildResults.java │ │ │ ├── FlogFileResults.java │ │ │ └── FlogMethodResults.java │ │ ├── railsNotes │ │ ├── RailsNotesBuildAction.java │ │ ├── RailsNotesParser.java │ │ ├── RailsNotesProjectAction.java │ │ ├── RailsNotesPublisher.java │ │ └── model │ │ │ ├── RailsNotesMetrics.java │ │ │ └── RailsNotesResults.java │ │ ├── railsStats │ │ ├── RailsStatsBuildAction.java │ │ ├── RailsStatsParser.java │ │ ├── RailsStatsProjectAction.java │ │ ├── RailsStatsPublisher.java │ │ └── model │ │ │ ├── RailsStatsMetrics.java │ │ │ └── RailsStatsResults.java │ │ └── rcov │ │ ├── RcovBuildAction.java │ │ ├── RcovParser.java │ │ ├── RcovProjectAction.java │ │ ├── RcovPublisher.java │ │ └── model │ │ ├── MetricTarget.java │ │ ├── RcovAbstractResult.java │ │ ├── RcovFileDetail.java │ │ ├── RcovFileResult.java │ │ ├── RcovResult.java │ │ └── Targets.java ├── resources │ ├── hudson │ │ └── plugins │ │ │ └── rubyMetrics │ │ │ ├── flog │ │ │ ├── FlogBuildAction │ │ │ │ └── index.jelly │ │ │ ├── FlogProjectAction │ │ │ │ ├── floatingBox.jelly │ │ │ │ └── nodata.jelly │ │ │ └── FlogPublisher │ │ │ │ └── config.jelly │ │ │ ├── railsNotes │ │ │ ├── RailsNotesBuildAction │ │ │ │ └── index.jelly │ │ │ ├── RailsNotesProjectAction │ │ │ │ ├── floatingBox.jelly │ │ │ │ └── nodata.jelly │ │ │ └── RailsNotesPublisher │ │ │ │ └── config.jelly │ │ │ ├── railsStats │ │ │ ├── RailsStatsBuildAction │ │ │ │ └── index.jelly │ │ │ ├── RailsStatsProjectAction │ │ │ │ ├── floatingBox.jelly │ │ │ │ └── nodata.jelly │ │ │ └── RailsStatsPublisher │ │ │ │ └── config.jelly │ │ │ ├── rcov │ │ │ ├── RcovBuildAction │ │ │ │ └── index.jelly │ │ │ ├── RcovProjectAction │ │ │ │ ├── floatingBox.jelly │ │ │ │ └── nodata.jelly │ │ │ ├── RcovPublisher │ │ │ │ └── config.jelly │ │ │ └── model │ │ │ │ ├── Messages.properties │ │ │ │ └── RcovFileDetail │ │ │ │ └── index.jelly │ │ │ └── tags │ │ │ ├── floatingBox.jelly │ │ │ ├── tableGraph.jelly │ │ │ └── taglib │ └── index.jelly ├── ruby │ ├── generator.rb │ └── templates │ │ ├── build_action.eruby │ │ ├── build_results.eruby │ │ ├── jelly │ │ ├── build_action.eruby │ │ ├── project_action_box.eruby │ │ ├── project_action_no_data.eruby │ │ └── publisher_config.eruby │ │ ├── project_action.eruby │ │ ├── publisher.eruby │ │ └── rails_task_publisher.eruby └── webapp │ ├── css │ └── style.css │ ├── images │ └── 24x24 │ │ └── tab.png │ ├── js │ └── flog.accordion.js │ ├── railsNotesHelp.html │ └── railsStatsHelp.html └── test ├── java └── hudson │ └── plugins │ └── rubyMetrics │ ├── flog │ ├── FlogExecutorTest.java │ └── FlogParserTest.java │ ├── railsNotes │ └── RailsNotesParserTest.java │ ├── railsStats │ └── RailsStatsParserTest.java │ └── rcov │ ├── RcovParserTest.java │ └── model │ ├── RcovFileDetailTest.java │ └── RcovFileResultTest.java └── resources └── hudson └── plugins └── rubyMetrics ├── flog ├── command_line_parser.rb └── flog-results-sample.txt └── rcov ├── index.html ├── index_0_9.html ├── lib-algebra_rb.html ├── lib-trinidad-core_ext_rb.html └── model ├── build.xml ├── index.html ├── lib-app-service_rb.html ├── lib-app-version_rb.html ├── lib-app_rb.html └── spec-app_spec_rb.html /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | work 3 | .settings 4 | .classpath 5 | .project 6 | .idea 7 | *.iml -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.6.5 2 | February 7, 2017 3 | 4 | * Add ability to use environmental variables in path to reports (#32) 5 | 6 | # 1.6.4 7 | September 22, 2016 8 | 9 | * Add support for Pipeline jobs (#28) 10 | 11 | # 1.6.3 12 | November 24, 2014 13 | 14 | * Enable plug-in to work with older and LTS Jenkins releases (#25). 15 | 16 | # 1.6.2 17 | September 2, 2014 18 | 19 | * Changes RcovFileResult class to avoid retaining the source code of every file in the coverage report in memory for every build. 20 | 21 | # 1.6.1 22 | September 2, 2014 23 | 24 | * Report value within empty range as 100%. 25 | * Files ending with spec are counted into the tests pool. 26 | 27 | # 1.6.0 28 | August 29, 2014 29 | 30 | * Upgrade to newer version of rake (1.8.0) and org.jenkins-ci.plugins (1.532.3) fixing JENKINS-22293 issue. 31 | * Migrate project configuration from Hudson to Jenkins style. 32 | 33 | # 1.5.0 34 | March 10, 2011 35 | 36 | * Do not abort when the build is unstable. 37 | * Graphs for ratios, classes, loc; no upper bounds. 38 | 39 | # 1.4.6 40 | November 27, 2010 41 | 42 | * Fix notes chart 43 | * Use an expandable textbox to set the flog directories 44 | 45 | # 1.4.5 46 | November 18, 2010 47 | 48 | * Fix: Flog graph doesn't track over time. 49 | 50 | # 1.4.4 51 | October 28, 2010 52 | 53 | * Add rake version range 54 | 55 | # 1.4 56 | January 5, 2010 57 | 58 | * Flog support added. 59 | * Fixes rcov parser support issues. 60 | 61 | # 1.3 62 | October 30, 2009 63 | 64 | * Rails notes support added. 65 | * Rails stats publisher allows to choose base directory. 66 | 67 | # 1.2.3 68 | May 4, 2009 69 | 70 | * Fixes some bugs. 71 | 72 | # 1.2.2 73 | September 24, 2008 74 | 75 | * Choose different rake version when we configure Rails stats publisher. 76 | * Added rcov metrics configuration and health report. 77 | 78 | # 1.2 79 | September 18, 2008 80 | 81 | * Rails stats support added. 82 | * Refactored to use common classes. 83 | 84 | # 1.1 85 | September 12, 2008 86 | 87 | * Solves a bug with the rcov report percentages. 88 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | /* `buildPlugin` step provided by: https://github.com/jenkins-infra/pipeline-library */ 4 | buildPlugin() 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RubyMetrics plugin 2 | 3 | Ruby metric reports for Jenkins. Supports Rcov, Rails stats, Rails notes and Flog 4 | 5 | See https://wiki.jenkins-ci.org/display/JENKINS/RubyMetrics+plugin for more information. 6 | 7 | 8 | ## Authors 9 | 10 | * [Piotr Kuczynski](http://github.com/pkuczynski) 11 | * David Calavera (retired) 12 | 13 | ## License 14 | 15 | RubyMetrics is released under the [MIT License](http://www.opensource.org/licenses/MIT). 16 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.jenkins-ci.plugins 7 | plugin 8 | 2.19 9 | 10 | 11 | 12 | hpi 13 | 14 | rubyMetrics 15 | 1.6.6-SNAPSHOT 16 | 17 | RubyMetrics plugin for Jenkins 18 | RubyMetrics plugin for Jenkins 19 | https://wiki.jenkins-ci.org/display/JENKINS/RubyMetrics+plugin 20 | 21 | 22 | 23 | MIT License 24 | http://opensource.org/licenses/MIT 25 | 26 | 27 | 28 | 29 | 30 | pkuczynski 31 | Piotr Kuczynski 32 | piotr.kuczynski@gmail.com 33 | Europe/Berlin 34 | 35 | 36 | david_calavera 37 | David Calavera 38 | calavera@apache.org 39 | 40 | retired 41 | 42 | 43 | 44 | 45 | 46 | false 47 | false 48 | 49 | 50 | 51 | 52 | org.jenkins-ci.plugins 53 | rake 54 | [1.8.0,) 55 | 56 | 57 | 58 | org.htmlparser 59 | htmlparser 60 | 1.6 61 | 62 | 63 | 64 | net.sourceforge.jregex 65 | jregex 66 | 1.2_01 67 | 68 | 69 | 70 | 71 | scm:git:git://github.com/jenkinsci/rubymetrics-plugin.git 72 | scm:git:git@github.com:jenkinsci/rubymetrics-plugin.git 73 | https://github.com/jenkins/rubymetrics-plugin 74 | HEAD 75 | 76 | 77 | 78 | 79 | 80 | org.jenkins-ci.tools 81 | maven-hpi-plugin 82 | 83 | 4.0 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-javadoc-plugin 89 | 90 | 8 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | disable-java8-doclint 99 | 100 | [1.8,) 101 | 102 | 103 | -Xdoclint:none 104 | 105 | 106 | 107 | 108 | 109 | 110 | repo.jenkins-ci.org 111 | https://repo.jenkins-ci.org/public/ 112 | 113 | 114 | 115 | 116 | 117 | repo.jenkins-ci.org 118 | https://repo.jenkins-ci.org/public/ 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/AbstractRailsTaskPublisher.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics; 2 | 3 | import hudson.FilePath; 4 | import hudson.Launcher; 5 | import hudson.model.AbstractBuild; 6 | import hudson.model.BuildListener; 7 | import hudson.model.StreamBuildListener; 8 | import hudson.plugins.rake.Rake; 9 | 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.IOException; 12 | 13 | public abstract class AbstractRailsTaskPublisher extends AbstractRubyMetricsPublisher { 14 | 15 | protected final Rake rake; 16 | 17 | protected final String rakeInstallation; 18 | 19 | protected final String rakeWorkingDir; 20 | 21 | private final String task; 22 | 23 | protected AbstractRailsTaskPublisher(String rakeInstallation, String rakeWorkingDir, String task) { 24 | this.rakeInstallation = rakeInstallation; 25 | this.rakeWorkingDir = rakeWorkingDir; 26 | this.task = task; 27 | this.rake = new Rake(this.rakeInstallation, null, task, null, this.rakeWorkingDir, true, true); 28 | } 29 | 30 | public String getRakeInstallation() { 31 | return rakeInstallation; 32 | } 33 | 34 | public String getRakeWorkingDir() { 35 | return rakeWorkingDir; 36 | } 37 | 38 | private boolean isRailsProject(FilePath workspace) { 39 | try { // relaxed rails app schema 40 | return workspace != null && workspace.isDirectory() && workspace.list("app") != null 41 | && workspace.list("config") != null && workspace.list("db") != null; 42 | } catch (Exception e) { 43 | return false; 44 | } 45 | } 46 | 47 | @Override 48 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) 49 | throws InterruptedException, IOException { 50 | FilePath workspace = build.getModuleRoot(); 51 | 52 | if (!isRailsProject(workspace)) { 53 | String message = "Your workspace is not a valid rails application directory"; 54 | if (workspace != null) { 55 | message += ": " + workspace.getName(); 56 | } 57 | return fail(build, listener, message); 58 | } 59 | 60 | listener.getLogger().println("Publishing rails " + task + " report..."); 61 | 62 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 63 | BuildListener stringListener = new StreamBuildListener(out); 64 | 65 | if (rake.perform(build, launcher, stringListener)) { 66 | buildAction(out, build); 67 | } else { 68 | return fail(build, listener, stringListener.toString()); 69 | } 70 | 71 | return true; 72 | } 73 | 74 | protected abstract void buildAction(ByteArrayOutputStream out, AbstractBuild build); 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/AbstractRubyMetricsBuildAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics; 2 | 3 | import hudson.model.*; 4 | import hudson.util.ChartUtil; 5 | import hudson.util.ColorPalette; 6 | import hudson.util.DataSetBuilder; 7 | import hudson.util.ShiftedCategoryAxis; 8 | import jenkins.tasks.SimpleBuildStep; 9 | import org.jfree.chart.ChartFactory; 10 | import org.jfree.chart.JFreeChart; 11 | import org.jfree.chart.axis.CategoryAxis; 12 | import org.jfree.chart.axis.CategoryLabelPositions; 13 | import org.jfree.chart.axis.NumberAxis; 14 | import org.jfree.chart.plot.CategoryPlot; 15 | import org.jfree.chart.plot.PlotOrientation; 16 | import org.jfree.chart.renderer.category.LineAndShapeRenderer; 17 | import org.jfree.chart.title.LegendTitle; 18 | import org.jfree.data.category.CategoryDataset; 19 | import org.jfree.ui.RectangleEdge; 20 | import org.jfree.ui.RectangleInsets; 21 | import org.kohsuke.stapler.StaplerRequest; 22 | import org.kohsuke.stapler.StaplerResponse; 23 | 24 | import java.awt.*; 25 | import java.io.IOException; 26 | import java.util.Calendar; 27 | 28 | @SuppressWarnings("unchecked") 29 | public abstract class AbstractRubyMetricsBuildAction implements HealthReportingAction, SimpleBuildStep.LastBuildAction { 30 | 31 | protected final Run owner; 32 | 33 | protected AbstractRubyMetricsBuildAction(Run owner) { 34 | this.owner = owner; 35 | } 36 | 37 | public T getPreviousResult() { 38 | Run b = owner; 39 | while (true) { 40 | b = b.getPreviousBuild(); 41 | if (b == null) 42 | return null; 43 | if (b.getResult() == Result.FAILURE) 44 | continue; 45 | AbstractRubyMetricsBuildAction r = b.getAction(this.getClass()); 46 | if (r != null) 47 | return (T) r; 48 | } 49 | } 50 | 51 | protected abstract DataSetBuilder getDataSetBuilder(); 52 | 53 | public void doGraph(StaplerRequest req, StaplerResponse rsp) throws IOException { 54 | if (shouldGenerateGraph(req, rsp)) { 55 | generateGraph(req, rsp, getDataSetBuilder()); 56 | } 57 | } 58 | 59 | protected void generateGraph(StaplerRequest req, StaplerResponse rsp, 60 | DataSetBuilder dsb) throws IOException { 61 | ChartUtil.generateGraph(req, rsp, createChart(dsb.build(), getRangeAxisLabel()), 500, 200); 62 | } 63 | 64 | protected boolean shouldGenerateGraph(StaplerRequest req, StaplerResponse rsp) 65 | throws IOException { 66 | if (ChartUtil.awtProblemCause != null) { 67 | rsp.sendRedirect2(req.getContextPath() + "/images/headless.png"); 68 | return false; 69 | } 70 | 71 | Calendar t = owner.getTimestamp(); 72 | 73 | if (req.checkIfModified(t, rsp)) { 74 | return false; // up to date 75 | } 76 | 77 | return true; 78 | } 79 | 80 | private JFreeChart createChart(CategoryDataset dataset, String rangeAxisLabel) { 81 | 82 | final JFreeChart chart = ChartFactory.createLineChart(null, // chart 83 | // title 84 | null, // unused 85 | rangeAxisLabel, // range axis label 86 | dataset, // data 87 | PlotOrientation.VERTICAL, // orientation 88 | true, // include legend 89 | true, // tooltips 90 | false // urls 91 | ); 92 | 93 | // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART... 94 | 95 | final LegendTitle legend = chart.getLegend(); 96 | legend.setPosition(RectangleEdge.RIGHT); 97 | 98 | chart.setBackgroundPaint(Color.white); 99 | 100 | final CategoryPlot plot = chart.getCategoryPlot(); 101 | 102 | // plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0)); 103 | plot.setBackgroundPaint(Color.WHITE); 104 | plot.setOutlinePaint(null); 105 | plot.setRangeGridlinesVisible(true); 106 | plot.setRangeGridlinePaint(Color.black); 107 | 108 | CategoryAxis domainAxis = new ShiftedCategoryAxis(null); 109 | plot.setDomainAxis(domainAxis); 110 | domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90); 111 | domainAxis.setLowerMargin(0.0); 112 | domainAxis.setUpperMargin(0.0); 113 | domainAxis.setCategoryMargin(0.0); 114 | 115 | final NumberAxis rangeAxis = getRangeAxis(plot); 116 | 117 | final LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer(); 118 | renderer.setBaseStroke(new BasicStroke(2.0f)); 119 | ColorPalette.apply(renderer); 120 | 121 | // crop extra space around the graph 122 | plot.setInsets(new RectangleInsets(5.0, 0, 0, 5.0)); 123 | 124 | return chart; 125 | } 126 | 127 | public Run getOwner() { 128 | return owner; 129 | } 130 | 131 | protected NumberAxis getRangeAxis(CategoryPlot plot) { 132 | NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); 133 | rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); 134 | // rangeAxis.setUpperBound(100); 135 | rangeAxis.setLowerBound(0); 136 | 137 | return rangeAxis; 138 | } 139 | 140 | protected String getRangeAxisLabel() { 141 | return ""; 142 | } 143 | 144 | public HealthReport getBuildHealth() { 145 | // TODO Auto-generated method stub 146 | return null; 147 | } 148 | 149 | public String getIconFileName() { 150 | return "graph.gif"; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/AbstractRubyMetricsProjectAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics; 2 | 3 | import hudson.model.*; 4 | import org.kohsuke.stapler.StaplerRequest; 5 | import org.kohsuke.stapler.StaplerResponse; 6 | 7 | import java.io.IOException; 8 | 9 | @SuppressWarnings("unchecked") 10 | public abstract class AbstractRubyMetricsProjectAction extends Actionable implements ProminentProjectAction { 11 | 12 | protected Job job; 13 | private transient AbstractProject project; // Retain backwards compatibility with old build records 14 | 15 | public AbstractRubyMetricsProjectAction(Job job) { 16 | this.job = job; 17 | } 18 | 19 | public Job getJob() { 20 | return job; 21 | } 22 | 23 | public String getIconFileName() { 24 | return "graph.gif"; 25 | } 26 | 27 | public String getSearchUrl() { 28 | return getUrlName(); 29 | } 30 | 31 | protected abstract Class getBuildActionClass(); 32 | 33 | public void doGraph(StaplerRequest req, StaplerResponse rsp) throws IOException { 34 | if (getLastResult() != null) { 35 | getLastResult().doGraph(req, rsp); 36 | } 37 | } 38 | 39 | public void doIndex(StaplerRequest req, StaplerResponse rsp) throws IOException { 40 | Integer buildNumber = getLastResultBuild(); 41 | if (buildNumber == null) { 42 | rsp.sendRedirect2("nodata"); 43 | } else { 44 | rsp.sendRedirect2("../" + buildNumber + "/" + getUrlName()); 45 | } 46 | } 47 | 48 | public T getLastResult() { 49 | for (Run r = job.getLastStableBuild(); r != null; r = r.getPreviousNotFailedBuild()) { 50 | if (r.getResult() == Result.FAILURE) 51 | continue; 52 | T result = r.getAction(getBuildActionClass()); 53 | if (result != null) 54 | return result; 55 | } 56 | return null; 57 | } 58 | 59 | public Integer getLastResultBuild() { 60 | for (Run r = job.getLastStableBuild(); r != null; r = r.getPreviousNotFailedBuild()) { 61 | if (r.getResult() == Result.FAILURE) 62 | continue; 63 | T result = r.getAction(getBuildActionClass()); 64 | if (result != null) 65 | return r.getNumber(); 66 | } 67 | return null; 68 | } 69 | 70 | private Object readResolve() { 71 | if (job == null) { 72 | job = project; 73 | } 74 | return this; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/AbstractRubyMetricsPublisher.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics; 2 | 3 | import hudson.model.*; 4 | import hudson.tasks.BuildStepMonitor; 5 | import hudson.tasks.Recorder; 6 | 7 | public abstract class AbstractRubyMetricsPublisher extends Recorder { 8 | 9 | protected boolean fail(Run run, TaskListener listener, String message) { 10 | listener.getLogger().println(message); 11 | run.setResult(Result.FAILURE); 12 | return true; 13 | } 14 | 15 | public BuildStepMonitor getRequiredMonitorService() { 16 | return BuildStepMonitor.BUILD; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/HtmlParser.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics; 2 | 3 | import hudson.model.BuildListener; 4 | import hudson.model.TaskListener; 5 | import org.htmlparser.Node; 6 | import org.htmlparser.Parser; 7 | import org.htmlparser.Text; 8 | import org.htmlparser.filters.NodeClassFilter; 9 | import org.htmlparser.tags.TableTag; 10 | import org.htmlparser.util.NodeList; 11 | import org.htmlparser.util.ParserException; 12 | 13 | import java.io.*; 14 | 15 | public abstract class HtmlParser { 16 | 17 | protected static final String TABLE_TAG_NAME = "table"; 18 | protected static final String TD_TAG_NAME = "td"; 19 | protected static final String TT_TAG_NAME = "tt"; 20 | protected static final String CLASS_ATTR_NAME = "class"; 21 | 22 | protected final File rootFilePath; 23 | protected TaskListener listener; 24 | 25 | public HtmlParser(File rootFilePath) { 26 | this.rootFilePath = rootFilePath; 27 | } 28 | 29 | protected String getHtml(InputStream input) throws IOException { 30 | StringWriter sw = new StringWriter(); 31 | PrintWriter pw = new PrintWriter(sw); 32 | 33 | BufferedReader reader = new BufferedReader(new InputStreamReader(input)); 34 | String line; 35 | while ((line = reader.readLine()) != null) { 36 | pw.write(line); 37 | } 38 | return sw.toString(); 39 | } 40 | 41 | protected Parser initParser(String html) throws ParserException { 42 | final Parser htmlParser = new Parser(); 43 | htmlParser.setInputHTML(html); 44 | return htmlParser; 45 | } 46 | 47 | protected String getTextAtNode(NodeList nodeList, int index) { 48 | Node node = nodeList.elementAt(index); 49 | 50 | NodeList textNode = new NodeList(); 51 | node.collectInto(textNode, new NodeClassFilter(Text.class)); 52 | 53 | return textNode.elementAt(0).getText(); 54 | } 55 | 56 | protected abstract TableTag getReportTable(Parser htmlParser) throws ParserException; 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/HtmlPublisher.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics; 2 | 3 | import hudson.FilePath; 4 | import hudson.model.*; 5 | 6 | import java.io.File; 7 | import java.io.FilenameFilter; 8 | 9 | import java.io.IOException; 10 | 11 | import static hudson.plugins.rubyMetrics.Utils.moveReportsToBuildRootDir; 12 | 13 | public abstract class HtmlPublisher extends AbstractRubyMetricsPublisher { 14 | 15 | protected String reportDir; 16 | 17 | public String getReportDir() { 18 | return reportDir; 19 | } 20 | 21 | protected boolean prepareMetricsReportBeforeParse(Run run, FilePath workspace, TaskListener listener, 22 | FilenameFilter indexFilter, String toolShortName) throws InterruptedException, IOException { 23 | 24 | final String reportDir = run.getEnvironment(listener).expand(this.reportDir); 25 | 26 | if (run.getResult() == Result.FAILURE) { 27 | listener.getLogger().println("Build failed, skipping " + toolShortName + " coverage report"); 28 | return true; 29 | } 30 | listener.getLogger().println("Publishing " + toolShortName + " report..."); 31 | 32 | boolean copied = moveReportsToBuildRootDir(workspace, run.getRootDir(), listener, reportDir, "**/*"); 33 | if (!copied) { 34 | run.setResult(Result.FAILURE); 35 | return fail(run, listener, toolShortName + " report directory wasn't found using the pattern '" + reportDir + "'."); 36 | } 37 | 38 | File[] coverageFiles = run.getRootDir().listFiles(indexFilter); 39 | if (coverageFiles == null || coverageFiles.length == 0) { 40 | return fail(run, listener, toolShortName + " report index file wasn't found"); 41 | } 42 | 43 | return true; 44 | } 45 | 46 | protected File[] getCoverageFiles(Run run, FilenameFilter indexFilter) { 47 | return run.getRootDir().listFiles(indexFilter); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/Utils.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics; 2 | 3 | import hudson.FilePath; 4 | import hudson.Util; 5 | import hudson.model.TaskListener; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | 10 | public class Utils { 11 | public static boolean moveReportsToBuildRootDir(FilePath workspace, File buildRootDir, TaskListener listener, String monitorDirectory, String copiesPattern) throws InterruptedException { 12 | return moveReportsToBuildRootDir(workspace, buildRootDir, listener, monitorDirectory, copiesPattern, false); 13 | } 14 | 15 | public static boolean moveReportsToBuildRootDir(FilePath workspace, File buildRootDir, TaskListener listener, String monitorDirectory, String copiesPattern, boolean sameLocation) throws InterruptedException { 16 | try { 17 | FilePath coverageDir = workspace.child(monitorDirectory); 18 | 19 | if (!coverageDir.exists()) { 20 | listener.getLogger().println("file not found: " + coverageDir); 21 | return false; 22 | } 23 | FilePath copiesLocation = new FilePath(buildRootDir); 24 | if (sameLocation) { 25 | copiesLocation = new FilePath(new File(buildRootDir, monitorDirectory)); 26 | } 27 | 28 | coverageDir.copyRecursiveTo(copiesPattern, copiesLocation); 29 | 30 | return true; 31 | } catch (IOException e) { 32 | Util.displayIOException(e, listener); 33 | e.printStackTrace(listener.fatalError("Unable to find coverage results")); 34 | } 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/flog/FlogBuildAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.flog; 2 | 3 | import hudson.model.AbstractBuild; 4 | import hudson.model.Action; 5 | import hudson.plugins.rubyMetrics.AbstractRubyMetricsBuildAction; 6 | import hudson.plugins.rubyMetrics.flog.model.FlogBuildResults; 7 | import hudson.util.ChartUtil; 8 | import hudson.util.ChartUtil.NumberOnlyBuildLabel; 9 | import hudson.util.DataSetBuilder; 10 | import org.jfree.chart.axis.NumberAxis; 11 | import org.jfree.chart.plot.CategoryPlot; 12 | 13 | import java.math.BigDecimal; 14 | import java.math.MathContext; 15 | import java.util.Collection; 16 | import java.util.Collections; 17 | 18 | public class FlogBuildAction extends AbstractRubyMetricsBuildAction { 19 | 20 | private final FlogBuildResults results; 21 | 22 | public FlogBuildAction(AbstractBuild owner, FlogBuildResults results) { 23 | super(owner); 24 | this.results = results; 25 | } 26 | 27 | public Collection getProjectActions() { 28 | return Collections.singletonList(new FlogProjectAction(owner.getParent())); 29 | } 30 | 31 | @Override 32 | protected DataSetBuilder getDataSetBuilder() { 33 | DataSetBuilder dsb = new DataSetBuilder(); 34 | 35 | for (FlogBuildAction action = this; action != null; action = action.getPreviousResult()) { 36 | ChartUtil.NumberOnlyBuildLabel label = new ChartUtil.NumberOnlyBuildLabel(action.owner); 37 | 38 | dsb.add(action.getResults().getTotal(), "Total score", label); 39 | dsb.add(action.getResults().getAverage(), "Average score", label); 40 | } 41 | 42 | return dsb; 43 | } 44 | 45 | @Override 46 | protected NumberAxis getRangeAxis(CategoryPlot plot) { 47 | NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); 48 | rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); 49 | rangeAxis.setUpperBound(new BigDecimal(results.getTotal()).round(MathContext.DECIMAL32).intValue() + 20); 50 | rangeAxis.setLowerBound(0); 51 | 52 | return rangeAxis; 53 | } 54 | 55 | 56 | 57 | public FlogBuildResults getResults() { 58 | return results; 59 | } 60 | 61 | public String getDisplayName() { 62 | return "Flog report"; 63 | } 64 | 65 | public String getUrlName() { 66 | return "flog"; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/flog/FlogExecutor.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.flog; 2 | 3 | import hudson.EnvVars; 4 | import hudson.FilePath; 5 | import hudson.Launcher; 6 | import hudson.util.ArgumentListBuilder; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.io.OutputStream; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | import static hudson.plugins.rubyMetrics.Utils.moveReportsToBuildRootDir; 16 | 17 | public class FlogExecutor { 18 | 19 | public boolean isFlogInstalled(Launcher launcher, EnvVars environment, FilePath workspace) { 20 | try { 21 | OutputStream out = launch(arguments("--help"), launcher, environment, workspace); 22 | 23 | return out != null; 24 | } catch (Exception e) { 25 | return false; 26 | } 27 | } 28 | 29 | public Map execute(String[] rbDirectories, Launcher launcher, EnvVars environment, FilePath workspace, File buildRootDir) throws InterruptedException, IOException { 30 | Map results = new HashMap(); 31 | 32 | for (String relativePath : rbDirectories) { 33 | if (workspace.child(relativePath) == null) { 34 | launcher.getListener().getLogger().println("the path: " + relativePath + " doesn't exist into the workpace, ignoring it"); 35 | continue; 36 | } 37 | 38 | FilePath[] rubyFiles = getRubyFiles(workspace, buildRootDir, relativePath, launcher); 39 | for (FilePath rubyFile : rubyFiles) { 40 | String rubyFilePath = rubyFile.toURI().getPath(); 41 | ArgumentListBuilder arguments = arguments("-ad", rubyFilePath); 42 | 43 | ByteArrayOutputStream out = launch(arguments, launcher, environment, workspace); 44 | if (out == null) { 45 | results.clear(); 46 | return results; 47 | } 48 | results.put(prettifyFilePath(relativePath, rubyFilePath), out); 49 | } 50 | } 51 | 52 | return results; 53 | } 54 | 55 | public ByteArrayOutputStream launch(ArgumentListBuilder arguments, Launcher launcher, EnvVars environment, FilePath workspace) throws InterruptedException, IOException { 56 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 57 | 58 | int result = launcher.launch() 59 | .cmds(arguments) 60 | .envs(environment) 61 | .stdout(out) 62 | .pwd(workspace) 63 | .join(); 64 | 65 | return result >= 0 ? out : null; 66 | } 67 | 68 | public ArgumentListBuilder arguments(String... args) { 69 | ArgumentListBuilder flogArguments = new ArgumentListBuilder(); 70 | flogArguments.add("flog"); 71 | for (String arg : args) { 72 | flogArguments.add(arg); 73 | } 74 | 75 | return flogArguments; 76 | } 77 | 78 | private FilePath[] getRubyFiles(FilePath workspace, File buildRootDir, String relativePath, Launcher launcher) throws InterruptedException, IOException { 79 | moveReportsToBuildRootDir(workspace, buildRootDir, launcher.getListener(), relativePath, "**/*.rb", true); 80 | 81 | FilePath classesLocation = new FilePath(new File(buildRootDir, relativePath)); 82 | launcher.getListener().getLogger().println("searching ruby classes into: " + classesLocation.toURI().getPath()); 83 | 84 | return classesLocation.list("**/*.rb"); 85 | } 86 | 87 | private String prettifyFilePath(String path, String rubyFilePath) { 88 | return rubyFilePath.substring(rubyFilePath.indexOf(path)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/flog/FlogParser.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.flog; 2 | 3 | import hudson.plugins.rubyMetrics.flog.model.FlogFileResults; 4 | import hudson.plugins.rubyMetrics.flog.model.FlogMethodResults; 5 | import jregex.Matcher; 6 | import jregex.Pattern; 7 | import org.apache.commons.lang.StringUtils; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.util.ArrayList; 11 | import java.util.Collection; 12 | 13 | public class FlogParser { 14 | 15 | private final static Pattern operatorRegex = new Pattern("\\s*({score}\\d+\\.\\d+):\\s({operator}.*)$"); 16 | private final static Pattern methodRegex = new Pattern("\\s*({score}\\d+\\.\\d+):\\s+({method}[A-Za-z:]+(?:#|::).*)"); 17 | 18 | public FlogFileResults parse(String filePath, ByteArrayOutputStream results) { 19 | return parse(filePath, results.toString()); 20 | } 21 | 22 | public FlogFileResults parse(String filePath, String results) { 23 | String[] resultsSplit = results.split("\n\n"); 24 | if (resultsSplit == null || resultsSplit.length == 0) { 25 | return null; 26 | } 27 | String[] totalAndAverage = resultsSplit[0].split("\n"); 28 | String total = getScoreFromOperator(totalAndAverage[0]); 29 | String average = getScoreFromOperator(totalAndAverage[1]); 30 | 31 | FlogFileResults flogResults = new FlogFileResults(total, average); 32 | for (int index = 1; index < resultsSplit.length; index++) { 33 | for (String line : resultsSplit[index].split("\n")) { 34 | addFlogResults(filePath, flogResults, line); 35 | } 36 | } 37 | 38 | return flogResults; 39 | } 40 | 41 | private String getScoreFromOperator(String line) { 42 | Matcher matcher = operatorRegex.matcher(line); 43 | return matcher.matches() ? matcher.group("score") : "0.0"; 44 | } 45 | 46 | private void addFlogResults(String filePath, FlogFileResults flogResults, String line) { 47 | Matcher matcher = methodRegex.matcher(line); 48 | if (matcher.matches()) { 49 | String methodName = prettifyMethodPath(filePath, matcher.group("method")); 50 | FlogMethodResults methodResults = new FlogMethodResults(methodName, matcher.group("score")); 51 | flogResults.addMethodResult(methodResults); 52 | } else { 53 | matcher = operatorRegex.matcher(line); 54 | if (matcher.matches()) { 55 | flogResults.addOperatorResult(matcher.group("operator"), matcher.group("score")); 56 | } 57 | } 58 | } 59 | 60 | private String prettifyMethodPath(String filePath, String methodName) { 61 | if (!methodName.contains(filePath)) { 62 | return methodName; 63 | } 64 | String[] split = methodName.split("\\s+"); 65 | 66 | Collection line = new ArrayList(); 67 | for (String s : split) { 68 | if (s.contains(filePath)) { 69 | s = s.substring(s.indexOf(filePath)); 70 | } 71 | line.add(s); 72 | } 73 | return StringUtils.join(line, " "); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/flog/FlogProjectAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.flog; 2 | 3 | import hudson.model.AbstractProject; 4 | import hudson.model.Job; 5 | import hudson.plugins.rubyMetrics.AbstractRubyMetricsProjectAction; 6 | 7 | public class FlogProjectAction extends AbstractRubyMetricsProjectAction { 8 | 9 | public FlogProjectAction(Job job) { 10 | super(job); 11 | } 12 | 13 | @Deprecated 14 | public FlogProjectAction(AbstractProject project) { 15 | super(project); 16 | } 17 | 18 | @Override 19 | protected Class getBuildActionClass() { 20 | return hudson.plugins.rubyMetrics.flog.FlogBuildAction.class; 21 | } 22 | 23 | public String getDisplayName() { 24 | return "Flog report"; 25 | } 26 | 27 | public String getUrlName() { 28 | return "flog"; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/flog/FlogPublisher.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.flog; 2 | 3 | import hudson.EnvVars; 4 | import hudson.Extension; 5 | import hudson.FilePath; 6 | import hudson.Launcher; 7 | import hudson.model.AbstractBuild; 8 | import hudson.model.AbstractProject; 9 | import hudson.model.Action; 10 | import hudson.model.BuildListener; 11 | import hudson.plugins.rubyMetrics.AbstractRubyMetricsPublisher; 12 | import hudson.plugins.rubyMetrics.flog.model.FlogBuildResults; 13 | import hudson.plugins.rubyMetrics.flog.model.FlogFileResults; 14 | import hudson.tasks.BuildStepDescriptor; 15 | import hudson.tasks.Publisher; 16 | import org.kohsuke.stapler.DataBoundConstructor; 17 | 18 | import java.io.ByteArrayOutputStream; 19 | import java.io.IOException; 20 | import java.util.Map; 21 | 22 | public class FlogPublisher extends AbstractRubyMetricsPublisher { 23 | 24 | private final String rbDirectories; 25 | private final String[] splittedDirectories; 26 | 27 | @DataBoundConstructor 28 | public FlogPublisher(String rbDirectories) { 29 | this.rbDirectories = rbDirectories; 30 | this.splittedDirectories = (this.rbDirectories != null && this.rbDirectories.length() > 0 ? this.rbDirectories : ".").split("[\t\r\n]+"); 31 | } 32 | 33 | public String getRbDirectories() { 34 | return rbDirectories; 35 | } 36 | 37 | @Override 38 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { 39 | final FlogExecutor flog = new FlogExecutor(); 40 | 41 | EnvVars environment = build.getEnvironment(listener); 42 | FilePath workspace = build.getModuleRoot(); 43 | 44 | if (!flog.isFlogInstalled(launcher, environment, workspace)) { 45 | return fail(build, listener, "Seems flog is not installed. Ensure flog is in your PATH"); 46 | } 47 | listener.getLogger().println("Publishing flog report..."); 48 | 49 | Map execResults = flog.execute(splittedDirectories, launcher, environment, workspace, build.getRootDir()); 50 | 51 | FlogBuildResults buildResults = buildResults(build, execResults); 52 | 53 | FlogBuildAction action = new FlogBuildAction(build, buildResults); 54 | build.getActions().add(action); 55 | 56 | return true; 57 | } 58 | 59 | private FlogBuildResults buildResults(AbstractBuild build, Map execResults) { 60 | final FlogParser parser = new FlogParser(); 61 | FlogBuildResults buildResults = new FlogBuildResults(); 62 | 63 | for (Map.Entry entry : execResults.entrySet()) { 64 | FlogFileResults resultsForFile = parser.parse(entry.getKey(), entry.getValue()); 65 | if (resultsForFile != null) { 66 | buildResults.addFileResults(entry.getKey(), resultsForFile); 67 | } 68 | } 69 | 70 | return buildResults; 71 | } 72 | 73 | @Override 74 | public BuildStepDescriptor getDescriptor() { 75 | return DESCRIPTOR; 76 | } 77 | 78 | @Extension 79 | public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); 80 | 81 | public static final class DescriptorImpl extends BuildStepDescriptor { 82 | 83 | @Override 84 | public boolean isApplicable(Class arg0) { 85 | return true; 86 | } 87 | 88 | @Override 89 | public String getDisplayName() { 90 | return "Publish Flog report"; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/flog/model/FlogBuildResults.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.flog.model; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class FlogBuildResults { 7 | private float total; 8 | private float average; 9 | private Map fileResults = new HashMap(); 10 | 11 | public float getTotal() { 12 | return total; 13 | } 14 | 15 | public float getAverage() { 16 | return average; 17 | } 18 | 19 | public Map getFileResults() { 20 | return fileResults; 21 | } 22 | 23 | public void setFileResults(Map fileResults) { 24 | this.fileResults = fileResults; 25 | } 26 | 27 | public void addFileResults(String file, FlogFileResults results) { 28 | fileResults.put(file, results); 29 | sumTotal(results.total); 30 | sumAverage(results.average); 31 | } 32 | 33 | private void sumTotal(float total) { 34 | this.total += total; 35 | } 36 | 37 | private void sumAverage(float average) { 38 | this.average += average; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/flog/model/FlogFileResults.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.flog.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class FlogFileResults { 7 | public final float total; 8 | public final float average; 9 | private List methodResults = new ArrayList(); 10 | 11 | public FlogFileResults(String total, String average) { 12 | this.total = Float.parseFloat(total); 13 | this.average = Float.parseFloat(average); 14 | } 15 | 16 | public List getMethodResults() { 17 | return methodResults; 18 | } 19 | 20 | public void setMethodResults(List methodResults) { 21 | this.methodResults = methodResults; 22 | } 23 | 24 | public void addMethodResult(FlogMethodResults result) { 25 | methodResults.add(result); 26 | } 27 | 28 | public void addOperatorResult(String name, String score) { 29 | if (!methodResults.isEmpty()) { 30 | methodResults.get(methodResults.size() - 1).addOperator(name, score); 31 | } 32 | } 33 | 34 | public float getTotal() { 35 | return total; 36 | } 37 | 38 | public float getAverage() { 39 | return average; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/flog/model/FlogMethodResults.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.flog.model; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class FlogMethodResults { 7 | public final String name; 8 | public final float score; 9 | private Map operatorResults = new HashMap(); 10 | 11 | public FlogMethodResults(String name, String score) { 12 | this.name = name; 13 | this.score = Float.parseFloat(score); 14 | } 15 | 16 | public Map getOperatorResults() { 17 | return operatorResults; 18 | } 19 | 20 | public void setOperatorResults(Map operators) { 21 | this.operatorResults = operators; 22 | } 23 | 24 | public void addOperator(String name, String score) { 25 | operatorResults.put(name, Float.valueOf(score)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/railsNotes/RailsNotesBuildAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsNotes; 2 | 3 | import hudson.model.Action; 4 | import hudson.model.AbstractBuild; 5 | import hudson.plugins.rubyMetrics.AbstractRubyMetricsBuildAction; 6 | import hudson.plugins.rubyMetrics.railsNotes.model.RailsNotesMetrics; 7 | import hudson.plugins.rubyMetrics.railsNotes.model.RailsNotesResults; 8 | import hudson.util.ChartUtil; 9 | import hudson.util.ChartUtil.NumberOnlyBuildLabel; 10 | import hudson.util.DataSetBuilder; 11 | 12 | import java.util.Collection; 13 | import java.util.Collections; 14 | import java.util.Map; 15 | 16 | public class RailsNotesBuildAction extends AbstractRubyMetricsBuildAction { 17 | private final RailsNotesResults results; 18 | 19 | public RailsNotesBuildAction(AbstractBuild owner, RailsNotesResults results) { 20 | super(owner); 21 | this.results = results; 22 | } 23 | 24 | public Collection getProjectActions() { 25 | return Collections.singletonList(new RailsNotesProjectAction(owner.getParent())); 26 | } 27 | 28 | public RailsNotesResults getResults() { 29 | return results; 30 | } 31 | 32 | public String getDisplayName() { 33 | return "Annotations (Rails notes)"; 34 | } 35 | 36 | public String getUrlName() { 37 | return "railsNotes"; 38 | } 39 | 40 | @Override 41 | protected DataSetBuilder getDataSetBuilder() { 42 | DataSetBuilder dsb = new DataSetBuilder(); 43 | 44 | for (RailsNotesBuildAction a = this; a != null; a = a.getPreviousResult()) { 45 | ChartUtil.NumberOnlyBuildLabel label = new ChartUtil.NumberOnlyBuildLabel(a.owner); 46 | 47 | for (Map.Entry entry : a.getResults().getTotal().entrySet()) { 48 | dsb.add(entry.getValue(), entry.getKey().toString(), label); 49 | } 50 | } 51 | 52 | return dsb; 53 | } 54 | 55 | @Override 56 | protected String getRangeAxisLabel() { 57 | return "Annotations"; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/railsNotes/RailsNotesParser.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsNotes; 2 | 3 | import hudson.plugins.rubyMetrics.railsNotes.model.RailsNotesMetrics; 4 | import hudson.plugins.rubyMetrics.railsNotes.model.RailsNotesResults; 5 | import org.apache.commons.lang.StringUtils; 6 | 7 | import java.io.ByteArrayOutputStream; 8 | import java.util.Arrays; 9 | import java.util.Collection; 10 | import java.util.Iterator; 11 | import java.util.LinkedHashSet; 12 | import java.util.regex.Pattern; 13 | 14 | public class RailsNotesParser { 15 | public RailsNotesResults parse(ByteArrayOutputStream output) { 16 | return parse(output.toString()); 17 | } 18 | 19 | public RailsNotesResults parse(String output) { 20 | RailsNotesResults response = new RailsNotesResults(); 21 | 22 | String[] aux = output.split("[\n\r]"); 23 | Collection lines = new LinkedHashSet(Arrays.asList(aux)); 24 | lines = removeSeparators(lines); 25 | 26 | // Fortunately a LinkedHashSet has a predictable order, so this can use the filenames 27 | Iterator linesIterator = lines.iterator(); 28 | String lastFile = ""; 29 | while (linesIterator.hasNext()) { 30 | String line = linesIterator.next(); 31 | if (StringUtils.isEmpty(line.trim())) continue; 32 | // Filename lines don't start with a space. Annotation lines do. 33 | if (line.charAt(0) != ' ') { 34 | lastFile = line.substring(0, line.length() - 1); // remove colon from end of line 35 | } else { 36 | for (RailsNotesMetrics metric : RailsNotesMetrics.values()) { 37 | // Match " * [line#] [ANNOTATION]" 38 | Pattern metricPattern = Pattern.compile("^ \\* \\[[\\s\\d]+\\] \\[" + metric.toString() + "\\]"); 39 | if (metricPattern.matcher(line).find()) { 40 | response.addAnnotationFor(lastFile, metric); 41 | break; // next line 42 | } 43 | } 44 | } 45 | } 46 | 47 | response.setOutput(output); 48 | return response; 49 | } 50 | 51 | private Collection removeSeparators(Collection lines) { 52 | Collection response = new LinkedHashSet(); 53 | for (String line : lines) { 54 | response.add(line.replaceAll("[\\r\\n+-]+", "")); 55 | } 56 | 57 | response.remove(""); 58 | return response; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/railsNotes/RailsNotesProjectAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsNotes; 2 | 3 | import hudson.model.AbstractProject; 4 | import hudson.model.Job; 5 | import hudson.plugins.rubyMetrics.AbstractRubyMetricsProjectAction; 6 | 7 | public class RailsNotesProjectAction extends AbstractRubyMetricsProjectAction { 8 | public RailsNotesProjectAction(Job job) { 9 | super(job); 10 | } 11 | 12 | @Deprecated 13 | public RailsNotesProjectAction(AbstractProject project) { 14 | super(project); 15 | } 16 | 17 | public String getDisplayName() { 18 | return "Annotations report"; 19 | } 20 | 21 | public String getUrlName() { 22 | return "railsNotes"; 23 | } 24 | 25 | @Override 26 | protected Class getBuildActionClass() { 27 | return hudson.plugins.rubyMetrics.railsNotes.RailsNotesBuildAction.class; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/railsNotes/RailsNotesPublisher.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsNotes; 2 | 3 | import hudson.Extension; 4 | import hudson.model.AbstractBuild; 5 | import hudson.model.AbstractProject; 6 | import hudson.model.Action; 7 | import hudson.plugins.rake.Rake; 8 | import hudson.plugins.rake.RubyInstallation; 9 | import hudson.plugins.rubyMetrics.AbstractRailsTaskPublisher; 10 | import hudson.plugins.rubyMetrics.railsNotes.model.RailsNotesResults; 11 | import hudson.tasks.BuildStepDescriptor; 12 | import hudson.tasks.Publisher; 13 | import org.kohsuke.stapler.DataBoundConstructor; 14 | 15 | import java.io.ByteArrayOutputStream; 16 | 17 | /** 18 | * Rails notes {@link Publisher} 19 | * 20 | * @author Adam Stegman 21 | */ 22 | public class RailsNotesPublisher extends AbstractRailsTaskPublisher { 23 | 24 | @DataBoundConstructor 25 | public RailsNotesPublisher(String rakeInstallation, String rakeWorkingDir) { 26 | super(rakeInstallation, rakeWorkingDir, "notes"); 27 | } 28 | 29 | protected void buildAction(ByteArrayOutputStream out, AbstractBuild build) { 30 | final RailsNotesParser parser = new RailsNotesParser(); 31 | RailsNotesResults results = parser.parse(out); 32 | 33 | RailsNotesBuildAction action = new RailsNotesBuildAction(build, results); 34 | build.getActions().add(action); 35 | } 36 | 37 | @Extension 38 | public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); 39 | 40 | public static final class DescriptorImpl extends BuildStepDescriptor { 41 | 42 | protected DescriptorImpl() { 43 | super(RailsNotesPublisher.class); 44 | } 45 | 46 | @Override 47 | public String getHelpFile() { 48 | return "/plugin/rubyMetrics/railsNotesHelp.html"; 49 | } 50 | 51 | @Override 52 | public String getDisplayName() { 53 | return "Publish Rails Notes report"; 54 | } 55 | 56 | public RubyInstallation[] getRakeInstallations() { 57 | return Rake.DESCRIPTOR.getInstallations(); 58 | } 59 | 60 | @Override 61 | public boolean isApplicable(Class jobType) { 62 | return true; 63 | } 64 | 65 | } 66 | 67 | @Override 68 | public BuildStepDescriptor getDescriptor() { 69 | return DESCRIPTOR; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/railsNotes/model/RailsNotesMetrics.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsNotes.model; 2 | 3 | import java.util.Arrays; 4 | import java.util.Comparator; 5 | 6 | public enum RailsNotesMetrics { 7 | TODO, FIXME, OPTIMIZE; 8 | 9 | public static RailsNotesMetrics toRailsNotesMetrics(String name) { 10 | try { 11 | return RailsNotesMetrics.valueOf(name.toUpperCase()); 12 | } catch (Exception e) { 13 | return null; 14 | } 15 | } 16 | 17 | public int getOrder() { 18 | return Arrays.asList(RailsNotesMetrics.values()).indexOf(this); 19 | } 20 | 21 | public static class COMPARATOR implements Comparator { 22 | public int compare(RailsNotesMetrics o1, RailsNotesMetrics o2) { 23 | return new Integer(o1.getOrder()).compareTo(new Integer(o2.getOrder())); 24 | } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/railsNotes/model/RailsNotesResults.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsNotes.model; 2 | 3 | import java.util.*; 4 | 5 | public class RailsNotesResults { 6 | private Map> metrics = new HashMap>(); 7 | private List sortedLabels = new ArrayList(); 8 | 9 | private class SortLabelsComparator implements Comparator { 10 | 11 | private final List labels; 12 | public SortLabelsComparator(List coll) { 13 | labels = coll; 14 | } 15 | 16 | public int compare(String o1, String o2) { 17 | return new Integer(labels.indexOf(o1)).compareTo(labels.indexOf(o2)); 18 | } 19 | 20 | } 21 | 22 | private String output; 23 | 24 | public Collection getHeaders() { 25 | Collection headers = new LinkedHashSet(); 26 | headers.add("Filename"); 27 | 28 | for (RailsNotesMetrics metric : RailsNotesMetrics.values()) { 29 | headers.add(metric.toString()); 30 | } 31 | 32 | return headers; 33 | } 34 | 35 | /** 36 | * Add an instance of annotation in the file fileName. This will 37 | * create an entry in metrics for fileName if one does not already 38 | * exist. 39 | * 40 | * @param fileName the filename from the output of Rails notes. 41 | * @param annotation the RailsNotesMetrics value associated with the annotation 42 | * in the report. 43 | */ 44 | public void addAnnotationFor(String fileName, RailsNotesMetrics annotation) { 45 | if (!sortedLabels.contains(fileName)) { 46 | sortedLabels.add(fileName); 47 | } 48 | 49 | // Create the map if it doesn't already exist, seed each annotation with a count of 0. 50 | if (!metrics.containsKey(fileName)) { 51 | Map metric = new TreeMap(); 52 | for (RailsNotesMetrics value : RailsNotesMetrics.values()) { 53 | metric.put(value, new Integer(0)); 54 | } 55 | metrics.put(fileName, metric); 56 | } 57 | // Get this file's map and add 1 to the count of that metric. 58 | Map metric = metrics.get(fileName); 59 | metric.put(annotation, new Integer(metric.get(annotation).intValue() + 1)); 60 | } 61 | 62 | /** 63 | * Generate the total count of each annotation by adding together all the annotations in 64 | * metrics. 65 | * 66 | * @return A Map to insert into metrics with each annotation and a 67 | * total count. 68 | */ 69 | public Map getTotal() { 70 | Map total = new TreeMap(); 71 | // Initialize all the values 72 | for (RailsNotesMetrics metric : RailsNotesMetrics.values()) total.put(metric, new Integer(0)); 73 | 74 | // For each class entered, add its counts to the total 75 | for (Map fileEntry : metrics.values()) { 76 | for (RailsNotesMetrics metric : fileEntry.keySet()) { 77 | total.put(metric, new Integer(total.get(metric).intValue() + fileEntry.get(metric).intValue())); 78 | } 79 | } 80 | 81 | return total; 82 | } 83 | 84 | public Map> getMetrics() { 85 | Comparator comparator = new SortLabelsComparator(sortedLabels); 86 | 87 | Map> response = 88 | new TreeMap>(comparator); 89 | 90 | for (Map.Entry> entry : metrics.entrySet()) { 91 | response.put(entry.getKey(), entry.getValue()); 92 | } 93 | 94 | // Include the total 95 | response.put("Total", getTotal()); 96 | 97 | return response; 98 | } 99 | 100 | /** 101 | * @return the output of the "rake notes" command. 102 | */ 103 | public String getOutput() { 104 | return output; 105 | } 106 | 107 | /** 108 | * @param output the output of the "rake notes" command. 109 | */ 110 | public void setOutput(String output) { 111 | this.output = output; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/railsStats/RailsStatsBuildAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsStats; 2 | 3 | import hudson.model.AbstractBuild; 4 | import hudson.model.Action; 5 | import hudson.plugins.rubyMetrics.AbstractRubyMetricsBuildAction; 6 | import hudson.plugins.rubyMetrics.railsStats.model.RailsStatsMetrics; 7 | import hudson.plugins.rubyMetrics.railsStats.model.RailsStatsResults; 8 | import hudson.util.ChartUtil; 9 | import hudson.util.ChartUtil.NumberOnlyBuildLabel; 10 | import hudson.util.DataSetBuilder; 11 | import org.kohsuke.stapler.StaplerRequest; 12 | import org.kohsuke.stapler.StaplerResponse; 13 | 14 | import java.io.IOException; 15 | import java.util.Collection; 16 | import java.util.Collections; 17 | import java.util.Map; 18 | 19 | public class RailsStatsBuildAction extends AbstractRubyMetricsBuildAction { 20 | 21 | private final RailsStatsResults results; 22 | 23 | public RailsStatsBuildAction(AbstractBuild owner, RailsStatsResults results) { 24 | super(owner); 25 | this.results = results; 26 | } 27 | 28 | public Collection getProjectActions() { 29 | return Collections.singletonList(new RailsStatsProjectAction(owner.getParent())); 30 | } 31 | 32 | public RailsStatsResults getResults() { 33 | return results; 34 | } 35 | 36 | public String getDisplayName() { 37 | return "Rails stats"; 38 | } 39 | 40 | public String getUrlName() { 41 | return "railsStats"; 42 | } 43 | 44 | public void doGraphClasses(StaplerRequest req, StaplerResponse rsp) throws IOException { 45 | if (shouldGenerateGraph(req, rsp)) { 46 | generateGraph(req, rsp, getDataSetBuilder(RailsStatsMetrics.CLASSES)); 47 | } 48 | } 49 | 50 | public void doGraphLoc(StaplerRequest req, StaplerResponse rsp) throws IOException { 51 | if (shouldGenerateGraph(req, rsp)) { 52 | generateGraph(req, rsp, getDataSetBuilder(RailsStatsMetrics.LOC)); 53 | } 54 | } 55 | 56 | @Override 57 | protected DataSetBuilder getDataSetBuilder() { 58 | return getDataSetBuilderRatios(); 59 | } 60 | 61 | protected DataSetBuilder getDataSetBuilder( 62 | RailsStatsMetrics metric) { 63 | DataSetBuilder dsb = new DataSetBuilder(); 64 | 65 | for (RailsStatsBuildAction a = this; a != null; a = a.getPreviousResult()) { 66 | ChartUtil.NumberOnlyBuildLabel buildLabel = new ChartUtil.NumberOnlyBuildLabel(a.owner); 67 | 68 | for (Map.Entry> entry : a.results.getMetrics() 69 | .entrySet()) { 70 | if (entry.getKey().equalsIgnoreCase("Total")) { 71 | continue; 72 | } 73 | dsb.add(entry.getValue().get(metric), entry.getKey(), buildLabel); 74 | } 75 | } 76 | 77 | return dsb; 78 | } 79 | 80 | protected DataSetBuilder getDataSetBuilderRatios() { 81 | DataSetBuilder dsb = new DataSetBuilder(); 82 | 83 | for (RailsStatsBuildAction a = this; a != null; a = a.getPreviousResult()) { 84 | ChartUtil.NumberOnlyBuildLabel buildLabel = new ChartUtil.NumberOnlyBuildLabel(a.owner); 85 | 86 | int sumLoc = 0, sumLines = 0, sumTestLoc = 0, sumClasses = 0, sumMethods = 0; 87 | 88 | for (Map.Entry> entry : a.results.getMetrics() 89 | .entrySet()) { 90 | String label = entry.getKey(); 91 | if (label.equalsIgnoreCase("Total")) { 92 | continue; 93 | } else if (label.endsWith(" tests") || label.endsWith(" specs")) { 94 | sumTestLoc += entry.getValue().get(RailsStatsMetrics.LOC); 95 | } else { 96 | sumLoc += entry.getValue().get(RailsStatsMetrics.LOC); 97 | sumLines += entry.getValue().get(RailsStatsMetrics.LINES); 98 | sumClasses += entry.getValue().get(RailsStatsMetrics.CLASSES); 99 | sumMethods += entry.getValue().get(RailsStatsMetrics.METHODS); 100 | } 101 | } 102 | 103 | dsb.add(sumLoc / (double) sumMethods, RailsStatsMetrics.LOC_M.prettyPrint(), buildLabel); 104 | dsb.add(sumMethods / (double) sumClasses, RailsStatsMetrics.M_C.prettyPrint(), 105 | buildLabel); 106 | dsb.add(sumTestLoc / (double) sumLoc, "Test/Code", buildLabel); 107 | dsb.add(sumLines / (double) sumLoc, "Lines/LOC", buildLabel); 108 | } 109 | 110 | return dsb; 111 | } 112 | 113 | protected DataSetBuilder getDataSetBuilderOriginal() { 114 | DataSetBuilder dsb = new DataSetBuilder(); 115 | 116 | for (RailsStatsBuildAction a = this; a != null; a = a.getPreviousResult()) { 117 | ChartUtil.NumberOnlyBuildLabel label = new ChartUtil.NumberOnlyBuildLabel(a.owner); 118 | 119 | for (Map.Entry entry : a.results.getTotal().entrySet()) { 120 | dsb.add(entry.getValue(), entry.getKey().prettyPrint(), label); 121 | } 122 | } 123 | 124 | return dsb; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/railsStats/RailsStatsParser.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsStats; 2 | 3 | import hudson.plugins.rubyMetrics.railsStats.model.RailsStatsMetrics; 4 | import hudson.plugins.rubyMetrics.railsStats.model.RailsStatsResults; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.util.*; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | public class RailsStatsParser { 12 | 13 | public RailsStatsResults parse(ByteArrayOutputStream output) { 14 | return parse(output.toString()); 15 | } 16 | 17 | public RailsStatsResults parse(String output) { 18 | RailsStatsResults response = new RailsStatsResults(); 19 | 20 | String[] aux = output.split("[\n\r]"); 21 | Collection lines = new LinkedHashSet(Arrays.asList(aux)); 22 | 23 | lines = removeSeparators(lines); 24 | 25 | Iterator linesIterator = lines.iterator(); 26 | String[] header = new String[1]; 27 | while (header.length < 2 && linesIterator.hasNext()) { 28 | header = cleanArray(linesIterator.next().split("[|]+")); //report header 29 | } 30 | 31 | while (linesIterator.hasNext()) { 32 | String line = linesIterator.next(); 33 | 34 | String[] columns = cleanArray(line.split("[|]+")); 35 | 36 | if (columns.length > 1) { //not last line 37 | Map metrics = new TreeMap(new RailsStatsMetrics.COMPARATOR()); 38 | 39 | for (int i = 1; i < header.length; i++) { //columns[0] == rails class type 40 | metrics.put(RailsStatsMetrics.toRailsStatsMetrics(header[i].trim()), Integer.valueOf(columns[i].trim())); 41 | } 42 | 43 | response.addMetric(columns[0].trim(), metrics); 44 | } else { 45 | Pattern pattern = Pattern.compile("CodeLOC:([0-9]+)TestLOC:([0-9]+)CodetoTestRatio:([0-9:.]+)"); 46 | Matcher matcher = pattern.matcher(columns[0].replaceAll("[\\s\\r\\n+-]+", "")); 47 | if (matcher.matches()) { 48 | response.setCodeLocSummary(matcher.group(1)); 49 | response.setTestLocSummary(matcher.group(2)); 50 | response.setCodeToTestRatio(matcher.group(3)); 51 | } 52 | } 53 | 54 | } 55 | 56 | return response; 57 | } 58 | 59 | private Collection removeSeparators(Collection lines) { 60 | Collection response = new LinkedHashSet(); 61 | for (String line : lines) { 62 | response.add(line.replaceAll("[\\r\\n+-]+", "")); 63 | } 64 | 65 | response.remove(""); 66 | return response; 67 | } 68 | 69 | private String[] cleanArray(String[] array) { 70 | Collection response = new ArrayList(Arrays.asList(array)); 71 | response.remove(""); 72 | return response.toArray(new String[response.size()]); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/railsStats/RailsStatsProjectAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsStats; 2 | 3 | import hudson.model.AbstractProject; 4 | import hudson.model.Job; 5 | import hudson.plugins.rubyMetrics.AbstractRubyMetricsProjectAction; 6 | 7 | public class RailsStatsProjectAction extends AbstractRubyMetricsProjectAction { 8 | 9 | public RailsStatsProjectAction(Job job) { 10 | super(job); 11 | } 12 | 13 | @Deprecated 14 | public RailsStatsProjectAction(AbstractProject project) { 15 | super(project); 16 | } 17 | 18 | public String getDisplayName() { 19 | return "Rails stats report"; 20 | } 21 | 22 | public String getUrlName() { 23 | return "railsStats"; 24 | } 25 | 26 | @Override 27 | protected Class getBuildActionClass() { 28 | return hudson.plugins.rubyMetrics.railsStats.RailsStatsBuildAction.class; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/railsStats/RailsStatsPublisher.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsStats; 2 | 3 | import hudson.Extension; 4 | import hudson.model.AbstractBuild; 5 | import hudson.model.AbstractProject; 6 | import hudson.model.Action; 7 | import hudson.plugins.rake.Rake; 8 | import hudson.plugins.rake.RubyInstallation; 9 | import hudson.plugins.rubyMetrics.AbstractRailsTaskPublisher; 10 | import hudson.plugins.rubyMetrics.railsStats.model.RailsStatsResults; 11 | import hudson.tasks.BuildStepDescriptor; 12 | import hudson.tasks.Publisher; 13 | import org.kohsuke.stapler.DataBoundConstructor; 14 | 15 | import java.io.ByteArrayOutputStream; 16 | 17 | /** 18 | * Rails stats {@link Publisher} 19 | * 20 | * @author David Calavera 21 | * 22 | */ 23 | @SuppressWarnings("unchecked") 24 | public class RailsStatsPublisher extends AbstractRailsTaskPublisher { 25 | 26 | @DataBoundConstructor 27 | public RailsStatsPublisher(String rakeInstallation, String rakeWorkingDir) { 28 | super(rakeInstallation, rakeWorkingDir, "stats"); 29 | } 30 | 31 | protected void buildAction(ByteArrayOutputStream out, AbstractBuild build) { 32 | final RailsStatsParser parser = new RailsStatsParser(); 33 | RailsStatsResults results = parser.parse(out); 34 | 35 | RailsStatsBuildAction action = new RailsStatsBuildAction(build, results); 36 | build.getActions().add(action); 37 | } 38 | 39 | @Extension 40 | public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); 41 | 42 | public static final class DescriptorImpl extends BuildStepDescriptor { 43 | 44 | protected DescriptorImpl() { 45 | super(RailsStatsPublisher.class); 46 | } 47 | 48 | @Override 49 | public String getHelpFile() { 50 | return "/plugin/rubyMetrics/railsStatsHelp.html"; 51 | } 52 | 53 | @Override 54 | public String getDisplayName() { 55 | return "Publish Rails stats report"; 56 | } 57 | 58 | public RubyInstallation[] getRakeInstallations() { 59 | return Rake.DESCRIPTOR.getInstallations(); 60 | } 61 | 62 | @Override 63 | public boolean isApplicable(Class jobType) { 64 | return true; 65 | } 66 | 67 | } 68 | 69 | @Override 70 | public BuildStepDescriptor getDescriptor() { 71 | return DESCRIPTOR; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/railsStats/model/RailsStatsMetrics.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsStats.model; 2 | 3 | import java.util.Arrays; 4 | import java.util.Comparator; 5 | 6 | public enum RailsStatsMetrics { 7 | LINES, LOC, CLASSES, METHODS, M_C, LOC_M; 8 | 9 | public String prettyPrint() { 10 | switch (this) { 11 | case LOC: 12 | return this.toString(); 13 | case M_C: 14 | return slashedPrint(); 15 | case LOC_M: 16 | return slashedPrint(); 17 | } 18 | return defaultPrettyPrint(); 19 | } 20 | 21 | private String defaultPrettyPrint() { 22 | String prettyString = this.toString().toLowerCase(); 23 | return prettyString.substring(0, 1).toUpperCase() + prettyString.substring(1); 24 | } 25 | 26 | private String slashedPrint() { 27 | return this.toString().replaceAll("_", "/"); 28 | } 29 | 30 | public static RailsStatsMetrics toRailsStatsMetrics(String name) { 31 | try { 32 | return RailsStatsMetrics.valueOf(name.toUpperCase().replaceAll("/", "_")); 33 | } catch (Exception e) { 34 | return null; 35 | } 36 | } 37 | 38 | public int getOrder() { 39 | return Arrays.asList(RailsStatsMetrics.values()).indexOf(this); 40 | } 41 | 42 | public static class COMPARATOR implements Comparator { 43 | public int compare(RailsStatsMetrics o1, RailsStatsMetrics o2) { 44 | return new Integer(o1.getOrder()).compareTo(new Integer(o2.getOrder())); 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/railsStats/model/RailsStatsResults.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsStats.model; 2 | 3 | import java.util.*; 4 | 5 | public class RailsStatsResults { 6 | 7 | private Map> metrics = new HashMap>(); 8 | private List sortedLabels = new ArrayList(); 9 | 10 | private class SortLabelsComparator implements Comparator { 11 | 12 | private final List sortedLabels; 13 | public SortLabelsComparator(List coll) { 14 | sortedLabels = coll; 15 | } 16 | 17 | public int compare(String o1, String o2) { 18 | return new Integer(sortedLabels.indexOf(o1)).compareTo(sortedLabels.indexOf(o2)); 19 | } 20 | 21 | } 22 | 23 | private String codeLocSummary; 24 | private String testLocSummary; 25 | private String codeToTestRatio; 26 | 27 | 28 | public Collection getHeaders() { 29 | Collection headers = new ArrayList(); 30 | headers.add("Name"); 31 | 32 | for (RailsStatsMetrics metric : RailsStatsMetrics.values()) { 33 | headers.add(metric.prettyPrint()); 34 | } 35 | 36 | return headers; 37 | } 38 | 39 | public void addMetric(String classType, Map metric) { 40 | metrics.put(classType, metric); 41 | if (!sortedLabels.contains(classType)) { 42 | sortedLabels.add(classType); 43 | } 44 | } 45 | 46 | public Map getTotal() { 47 | return metrics.get("Total"); 48 | } 49 | 50 | public Map> getMetrics() { 51 | Comparator comparator = new SortLabelsComparator(sortedLabels); 52 | 53 | Map> response = 54 | new TreeMap>(comparator); 55 | 56 | for (Map.Entry> entry : metrics.entrySet()) { 57 | response.put(entry.getKey(), entry.getValue()); 58 | } 59 | 60 | return response; 61 | } 62 | public void setMetrics(Map> metrics) { 63 | this.metrics = metrics; 64 | } 65 | public String getCodeLocSummary() { 66 | return codeLocSummary; 67 | } 68 | public void setCodeLocSummary(String codeLocSummary) { 69 | this.codeLocSummary = codeLocSummary; 70 | } 71 | public String getTestLocSummary() { 72 | return testLocSummary; 73 | } 74 | public void setTestLocSummary(String testLocSummary) { 75 | this.testLocSummary = testLocSummary; 76 | } 77 | public String getCodeToTestRatio() { 78 | return codeToTestRatio; 79 | } 80 | public void setCodeToTestRatio(String codeToTestRatio) { 81 | this.codeToTestRatio = codeToTestRatio; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/rcov/RcovBuildAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.rcov; 2 | 3 | import hudson.model.AbstractBuild; 4 | import hudson.model.Action; 5 | import hudson.model.HealthReport; 6 | import hudson.model.Run; 7 | import hudson.plugins.rubyMetrics.AbstractRubyMetricsBuildAction; 8 | import hudson.plugins.rubyMetrics.rcov.model.*; 9 | import hudson.util.ChartUtil; 10 | import hudson.util.ChartUtil.NumberOnlyBuildLabel; 11 | import hudson.util.DataSetBuilder; 12 | import org.kohsuke.stapler.StaplerRequest; 13 | import org.kohsuke.stapler.StaplerResponse; 14 | 15 | import java.util.Collection; 16 | import java.util.Collections; 17 | import java.util.List; 18 | 19 | public class RcovBuildAction extends AbstractRubyMetricsBuildAction { 20 | 21 | private final RcovResult results; 22 | private final List targets; 23 | 24 | public RcovBuildAction(Run owner, RcovResult results, List targets) { 25 | super(owner); 26 | this.results = results; 27 | this.targets = targets; 28 | } 29 | 30 | public Collection getProjectActions() { 31 | return Collections.singletonList(new RcovProjectAction(owner.getParent())); 32 | } 33 | 34 | public HealthReport getBuildHealth() { 35 | int minValue = 100; 36 | Targets minMetric = null; 37 | for (MetricTarget target : targets) { 38 | int value = calcRangeScore(target.getHealthy(), target.getUnhealthy(), 39 | results.getRatioFloat(target.getMetric()).intValue()); 40 | if (value <= minValue) { 41 | minValue = value; 42 | minMetric = target.getMetric(); 43 | } 44 | } 45 | HealthReport report = minMetric != null?new HealthReport(minValue, results.getHealthDescription(minMetric)):null; 46 | return report; 47 | } 48 | 49 | public String getDisplayName() { 50 | return "Rcov report"; 51 | } 52 | 53 | public String getUrlName() { 54 | return "rcov"; 55 | } 56 | 57 | public RcovResult getResults() { 58 | return results; 59 | } 60 | 61 | private int calcRangeScore(Integer max, Integer min, int value) { 62 | if (min == null || min < 0) min = 0; 63 | if (max == null || max > 100) max = 100; 64 | if (min >= max) min = max - 1; 65 | int result = (int) (100f * (value - min.floatValue()) / (max.floatValue() - min.floatValue())); 66 | if (result < 0) return 0; 67 | if (result > 100) return 100; 68 | return result; 69 | } 70 | 71 | public Object getDynamic(final String link, final StaplerRequest request, final StaplerResponse response) { 72 | if (link.startsWith("file.")) { 73 | String file = link.substring(link.indexOf("file.") + 5); 74 | RcovFileResult fileResult = getResults().getFile(file); 75 | return new RcovFileDetail(owner, fileResult); 76 | } 77 | 78 | return null; 79 | } 80 | 81 | @Override 82 | protected DataSetBuilder getDataSetBuilder() { 83 | DataSetBuilder dsb = new DataSetBuilder(); 84 | 85 | for (RcovBuildAction a = this; a != null; a = a.getPreviousResult()) { 86 | ChartUtil.NumberOnlyBuildLabel label = new ChartUtil.NumberOnlyBuildLabel(a.owner); 87 | 88 | dsb.add(a.getResults().getTotalCoverageFloat(), "total coverage", label); 89 | dsb.add(a.getResults().getCodeCoverageFloat(), "code coverage", label); 90 | } 91 | return dsb; 92 | } 93 | 94 | @Override 95 | protected String getRangeAxisLabel() { 96 | return "%"; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/rcov/RcovParser.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.rcov; 2 | 3 | import hudson.plugins.rubyMetrics.HtmlParser; 4 | import hudson.plugins.rubyMetrics.rcov.model.RcovFileResult; 5 | import hudson.plugins.rubyMetrics.rcov.model.RcovResult; 6 | import hudson.util.IOException2; 7 | import org.htmlparser.Node; 8 | import org.htmlparser.Parser; 9 | import org.htmlparser.Text; 10 | import org.htmlparser.filters.AndFilter; 11 | import org.htmlparser.filters.HasAttributeFilter; 12 | import org.htmlparser.filters.NodeClassFilter; 13 | import org.htmlparser.filters.TagNameFilter; 14 | import org.htmlparser.tags.LinkTag; 15 | import org.htmlparser.tags.TableColumn; 16 | import org.htmlparser.tags.TableRow; 17 | import org.htmlparser.tags.TableTag; 18 | import org.htmlparser.util.NodeList; 19 | import org.htmlparser.util.ParserException; 20 | 21 | import java.io.*; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | public class RcovParser extends HtmlParser { 26 | 27 | private static final String REPORT_CLASS_VALUE = "report"; 28 | 29 | public RcovParser(File rootFilePath) { 30 | super(rootFilePath); 31 | } 32 | 33 | public RcovResult parse(File file) throws IOException { 34 | return parse(new FileInputStream(file)); 35 | } 36 | 37 | public RcovResult parse(InputStream input) throws IOException { 38 | try { 39 | RcovResult result = new RcovResult(); 40 | 41 | Parser parser = initParser(getHtml(input)); 42 | TableTag report = getReportTable(parser); 43 | 44 | if (report.getRowCount() > 0) { 45 | //row at 0 is the header row, so we have to get the row at 1 46 | TableRow totalRow = report.getRow(1); 47 | TableColumn[] columns = totalRow.getColumns(); 48 | 49 | result.setTotalLines(getTextFromTT(columns[1])); 50 | result.setCodeLines(getTextFromTT(columns[2])); 51 | result.setTotalCoverage(getTextFromTT(columns[3])); 52 | result.setCodeCoverage(getTextFromTT(columns[4])); 53 | 54 | for (int i = 2; i < report.getRowCount(); i++) { 55 | result.addFile(parseRow(report.getRow(i))); 56 | } 57 | } 58 | 59 | return result; 60 | } catch (Exception e) { 61 | throw new IOException2("cannot parse rcov report file", e); 62 | } 63 | } 64 | 65 | protected TableTag getReportTable(Parser htmlParser) throws ParserException { 66 | final AndFilter filter = new AndFilter(new TagNameFilter(TABLE_TAG_NAME), 67 | new HasAttributeFilter(CLASS_ATTR_NAME, REPORT_CLASS_VALUE)); 68 | 69 | NodeList reportNode = htmlParser.extractAllNodesThatMatch(filter); 70 | if (!(reportNode != null && reportNode.size() > 0)) { 71 | throw new ParserException("cannot parse rcov report file, report element wasn't found"); 72 | } 73 | return (TableTag) reportNode.elements().nextNode(); 74 | } 75 | 76 | private RcovFileResult parseRow(TableRow row) throws ParserException, IOException { 77 | final RcovFileResult file = new RcovFileResult(); 78 | 79 | NodeList nodeList = new NodeList(); 80 | row.collectInto(nodeList, new TagNameFilter("a")); 81 | String linkPath = null; 82 | if (nodeList.size() > 0) { 83 | LinkTag link = (LinkTag) nodeList.elementAt(0); 84 | linkPath = link.getLink(); 85 | file.setHref(link.getLink().replaceAll(".html", "")); 86 | file.setName(link.getLinkText()); 87 | } 88 | 89 | TableColumn[] columns = row.getColumns(); 90 | file.setTotalLines(getTextFromTT(columns[1])); 91 | file.setCodeLines(getTextFromTT(columns[2])); 92 | file.setTotalCoverage(getTextFromTT(columns[3])); 93 | file.setCodeCoverage(getTextFromTT(columns[4])); 94 | 95 | return file; 96 | } 97 | 98 | private String getTextFromTT(TableColumn td) { 99 | NodeList nodeList = new NodeList(); 100 | td.collectInto(nodeList, new TagNameFilter(TT_TAG_NAME)); 101 | 102 | return getTextFromFirstNode(nodeList); 103 | } 104 | 105 | private String getTextFromFirstNode(NodeList nodeList) { 106 | String text = null; 107 | 108 | if (nodeList.size() > 0) { 109 | Node first = nodeList.elementAt(0); 110 | nodeList = new NodeList(); 111 | Node parent = first.getChildren() != null && first.getChildren().size() > 0?first:first.getParent(); 112 | 113 | parent.collectInto(nodeList, new NodeClassFilter(Text.class)); 114 | text = nodeList.toHtml(true).replaceAll(" ", "").trim(); 115 | } 116 | 117 | return text; 118 | } 119 | 120 | public String parseSource(String href) throws ParserException, IOException { 121 | String source = null; 122 | 123 | File[] sourceFile = rootFilePath.listFiles(new RcovFilenameFilter(href)); 124 | 125 | if (sourceFile != null && sourceFile.length > 0 && sourceFile[0].exists()) { 126 | String html = getHtml(new FileInputStream(sourceFile[0])); 127 | 128 | String sourceFromDetails = parseSourceInTableDetails(html); 129 | if (sourceFromDetails != null) { 130 | source = sourceFromDetails; 131 | } else { 132 | source = parseSourceFromRegularExpression(html); 133 | } 134 | } 135 | 136 | return source; 137 | } 138 | 139 | private String parseSourceInTableDetails(String html) throws FileNotFoundException, ParserException, IOException { 140 | String source = null; 141 | 142 | final Parser htmlParser = initParser(html); 143 | final AndFilter filter = new AndFilter(new TagNameFilter(TABLE_TAG_NAME), 144 | new HasAttributeFilter(CLASS_ATTR_NAME, "details")); 145 | 146 | NodeList reportNode = htmlParser.extractAllNodesThatMatch(filter); 147 | if (reportNode != null && reportNode.size() > 0) { 148 | source = ((TableTag) reportNode.elements().nextNode()).toHtml(true); 149 | } 150 | 151 | return source; 152 | } 153 | 154 | private String parseSourceFromRegularExpression(String html) { 155 | String source = null; 156 | 157 | Pattern pattern = Pattern.compile(".+.+(
(.+)
).+"); 158 | Matcher matcher = pattern.matcher(html); 159 | if (matcher.matches()) { 160 | source = matcher.group(1); 161 | } 162 | 163 | return source; 164 | } 165 | 166 | private static class RcovFilenameFilter implements FilenameFilter { 167 | String fileName; 168 | public RcovFilenameFilter(String fileName) { 169 | this.fileName = fileName; 170 | } 171 | 172 | public boolean accept(File dir, String name) { 173 | return name.equalsIgnoreCase(fileName); 174 | } 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/rcov/RcovProjectAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.rcov; 2 | 3 | import hudson.model.AbstractProject; 4 | import hudson.model.Job; 5 | import hudson.plugins.rubyMetrics.AbstractRubyMetricsProjectAction; 6 | 7 | public class RcovProjectAction extends AbstractRubyMetricsProjectAction { 8 | 9 | public RcovProjectAction(Job job) { 10 | super(job); 11 | } 12 | 13 | @Deprecated 14 | public RcovProjectAction(AbstractProject project) { 15 | super(project); 16 | } 17 | 18 | public String getDisplayName() { 19 | return "Rcov report"; 20 | } 21 | 22 | public String getUrlName() { 23 | return "rcov"; 24 | } 25 | 26 | @Override 27 | protected Class getBuildActionClass() { 28 | return hudson.plugins.rubyMetrics.rcov.RcovBuildAction.class; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/rcov/RcovPublisher.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.rcov; 2 | 3 | import hudson.Extension; 4 | import hudson.FilePath; 5 | import hudson.Launcher; 6 | import hudson.model.*; 7 | import hudson.plugins.rubyMetrics.HtmlPublisher; 8 | import hudson.plugins.rubyMetrics.rcov.model.MetricTarget; 9 | import hudson.plugins.rubyMetrics.rcov.model.RcovResult; 10 | import hudson.plugins.rubyMetrics.rcov.model.Targets; 11 | import hudson.tasks.BuildStepDescriptor; 12 | import hudson.tasks.Publisher; 13 | import jenkins.tasks.SimpleBuildStep; 14 | import net.sf.json.JSONObject; 15 | import org.apache.commons.beanutils.ConvertUtils; 16 | import org.kohsuke.stapler.DataBoundConstructor; 17 | import org.kohsuke.stapler.DataBoundSetter; 18 | import org.kohsuke.stapler.StaplerRequest; 19 | 20 | import javax.annotation.Nonnull; 21 | import java.io.File; 22 | import java.io.FilenameFilter; 23 | import java.io.IOException; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | /** 28 | * Rcov {@link Publisher} 29 | * 30 | * @author David Calavera 31 | * 32 | */ 33 | @SuppressWarnings({"unchecked", "serial"}) 34 | public class RcovPublisher extends HtmlPublisher implements SimpleBuildStep { 35 | 36 | private List targets = new ArrayList(){{ 37 | add(new MetricTarget(Targets.TOTAL_COVERAGE, 80, null, null)); 38 | add(new MetricTarget(Targets.CODE_COVERAGE, 80, null, null)); 39 | }}; 40 | 41 | @DataBoundConstructor 42 | public RcovPublisher(String reportDir) { 43 | this.reportDir = reportDir; 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | @Override 50 | public void perform(@Nonnull Run run, @Nonnull FilePath workspace, @Nonnull Launcher launcher, 51 | @Nonnull TaskListener listener) throws InterruptedException, IOException { 52 | final RcovFilenameFilter indexFilter = new RcovFilenameFilter(); 53 | prepareMetricsReportBeforeParse(run, workspace, listener, indexFilter, DESCRIPTOR.getToolShortName()); 54 | if (run.getResult() == Result.FAILURE) { 55 | return; 56 | } 57 | 58 | RcovParser parser = new RcovParser(run.getRootDir()); 59 | RcovResult results = parser.parse(getCoverageFiles(run, indexFilter)[0]); 60 | 61 | RcovBuildAction action = new RcovBuildAction(run, results, targets); 62 | run.getActions().add(action); 63 | 64 | if (failMetrics(results, listener)) { 65 | run.setResult(Result.UNSTABLE); 66 | } 67 | } 68 | 69 | private boolean failMetrics(RcovResult results, TaskListener listener) { 70 | float initRatio = 0; 71 | float resultRatio = 0; 72 | for (MetricTarget target : targets) { 73 | initRatio = target.getUnstable(); 74 | resultRatio = results.getRatioFloat(target.getMetric()); 75 | 76 | if (resultRatio < initRatio) { 77 | listener.getLogger().println("Code coverage enforcement failed for the following metrics:"); 78 | listener.getLogger().println(" " + target.getMetric().getName()); 79 | return true; 80 | } 81 | } 82 | return false; 83 | } 84 | 85 | public List getTargets() { 86 | return targets; 87 | } 88 | 89 | @DataBoundSetter 90 | public void setTargets(List targets) { 91 | this.targets = targets; 92 | } 93 | 94 | /** 95 | * Descriptor should be singleton. 96 | */ 97 | @Extension 98 | public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); 99 | 100 | public static final class DescriptorImpl extends BuildStepDescriptor { 101 | 102 | private final List targets; 103 | 104 | protected DescriptorImpl() { 105 | super(RcovPublisher.class); 106 | targets = new ArrayList(){{ 107 | add(new MetricTarget(Targets.TOTAL_COVERAGE, 80, null, null)); 108 | add(new MetricTarget(Targets.CODE_COVERAGE, 80, null, null)); 109 | }}; 110 | } 111 | 112 | public String getToolShortName() { 113 | return "rcov"; 114 | } 115 | 116 | @Override 117 | public String getDisplayName() { 118 | return "Publish Rcov report"; 119 | } 120 | 121 | public List getTargets(RcovPublisher instance) { 122 | return instance != null && instance.getTargets() != null?instance.getTargets() : getDefaultTargets(); 123 | } 124 | 125 | private List getDefaultTargets() { 126 | return targets; 127 | } 128 | 129 | @Override 130 | public boolean isApplicable(Class jobType) { 131 | return true; 132 | } 133 | 134 | @Override 135 | public RcovPublisher newInstance(StaplerRequest req, JSONObject formData) throws FormException { 136 | RcovPublisher instance = req.bindParameters(RcovPublisher.class, "rcov."); 137 | 138 | ConvertUtils.register(MetricTarget.CONVERTER, Targets.class); 139 | List targets = req.bindParametersToList(MetricTarget.class, "rcov.target."); 140 | instance.setTargets(targets); 141 | return instance; 142 | } 143 | 144 | } 145 | 146 | @Override 147 | public BuildStepDescriptor getDescriptor() { 148 | return DESCRIPTOR; 149 | } 150 | 151 | private static class RcovFilenameFilter implements FilenameFilter { 152 | public boolean accept(File dir, String name) { 153 | return name.equalsIgnoreCase("index.html"); 154 | } 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/rcov/model/MetricTarget.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.rcov.model; 2 | 3 | import org.apache.commons.beanutils.Converter; 4 | import org.kohsuke.stapler.DataBoundConstructor; 5 | 6 | public class MetricTarget { 7 | 8 | private final Targets metric; 9 | private final Integer healthy; 10 | private final Integer unhealthy; 11 | private final Integer unstable; 12 | 13 | public static final TargetConverter CONVERTER = new TargetConverter(); 14 | 15 | /** 16 | * @param metric 17 | * @param healthy 18 | * @param unhealthy 19 | * @param unstable 20 | * @stapler-constructor 21 | */ 22 | @DataBoundConstructor 23 | public MetricTarget(Targets metric, Integer healthy, Integer unhealthy, Integer unstable) { 24 | this.metric = metric; 25 | this.healthy = healthy != null?healthy:80; 26 | this.unhealthy = unhealthy; 27 | this.unstable = unstable; 28 | } 29 | 30 | public Targets getMetric() { 31 | return metric; 32 | } 33 | 34 | public Integer getHealthy() { 35 | return healthy != null?healthy:0; 36 | } 37 | 38 | public Integer getUnhealthy() { 39 | return unhealthy != null?unhealthy:0; 40 | } 41 | 42 | public Integer getUnstable() { 43 | return unstable != null?unstable:0; 44 | } 45 | 46 | private static class TargetConverter implements Converter { 47 | public Object convert(Class type, Object value) { 48 | return Targets.resolve(value.toString()); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/rcov/model/RcovAbstractResult.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.rcov.model; 2 | 3 | import org.apache.commons.lang.StringUtils; 4 | 5 | import java.math.BigDecimal; 6 | 7 | public class RcovAbstractResult { 8 | 9 | private String totalLines; 10 | private String codeLines; 11 | private String totalCoverage; 12 | private String codeCoverage; 13 | 14 | public Integer getTotalLinesInteger() { 15 | return Integer.valueOf(getTotalLines()); 16 | } 17 | 18 | public String getTotalLines() { 19 | return totalLines; 20 | } 21 | public void setTotalLines(String totalLines) { 22 | this.totalLines = totalLines; 23 | } 24 | 25 | public Integer getCodeLinesInteger() { 26 | return Integer.valueOf(getCodeLines()); 27 | } 28 | 29 | public String getCodeLines() { 30 | return codeLines; 31 | } 32 | public void setCodeLines(String codeLines) { 33 | this.codeLines = codeLines; 34 | } 35 | 36 | public Float getTotalCoverageFloat() { 37 | return StringUtils.isEmpty(totalCoverage)? 0 : Float.valueOf(totalCoverage.replaceAll("%", "")); 38 | } 39 | 40 | public String getTotalCoverage() { 41 | return totalCoverage; 42 | } 43 | public void setTotalCoverage(String totalCoverage) { 44 | this.totalCoverage = totalCoverage; 45 | } 46 | 47 | public Float getCodeCoverageFloat() { 48 | return StringUtils.isEmpty(codeCoverage)? 0 : Float.valueOf(codeCoverage.replaceAll("%", "")); 49 | } 50 | 51 | public String getCodeCoverage() { 52 | return codeCoverage; 53 | } 54 | public void setCodeCoverage(String codeCoverage) { 55 | this.codeCoverage = codeCoverage; 56 | } 57 | 58 | public String getTotalCoveredWidth() { 59 | return new BigDecimal(getTotalCoverageFloat()).setScale(0, BigDecimal.ROUND_HALF_DOWN).toString(); 60 | } 61 | 62 | public String getTotalUncoveredWidth() { 63 | return String.valueOf(100 - Integer.valueOf(getTotalCoveredWidth())); 64 | } 65 | 66 | public String getCodeCoveredWidth() { 67 | return new BigDecimal(getCodeCoverageFloat()).setScale(0, BigDecimal.ROUND_HALF_DOWN).toString(); 68 | } 69 | 70 | public String getCodeUncoveredWidth() { 71 | return String.valueOf(100 - Integer.valueOf(getCodeCoveredWidth())); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/rcov/model/RcovFileDetail.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.rcov.model; 2 | 3 | import hudson.model.AbstractBuild; 4 | import hudson.model.ModelObject; 5 | import hudson.model.Run; 6 | import hudson.plugins.rubyMetrics.rcov.RcovParser; 7 | import org.htmlparser.util.ParserException; 8 | 9 | import java.io.IOException; 10 | import java.io.Serializable; 11 | import java.util.logging.Logger; 12 | 13 | import static java.util.logging.Level.SEVERE; 14 | 15 | public class RcovFileDetail implements ModelObject, Serializable { 16 | 17 | private static final long serialVersionUID = -3496008428347123532L; 18 | 19 | private static final Logger LOGGER = Logger.getLogger(RcovFileDetail.class.getName()); 20 | 21 | private final Run owner; 22 | private final RcovFileResult result; 23 | 24 | public RcovFileDetail(final Run owner, final RcovFileResult result) { 25 | this.owner = owner; 26 | this.result = result; 27 | } 28 | 29 | public static long getSerialVersionUID() { 30 | return serialVersionUID; 31 | } 32 | 33 | public Run getOwner() { 34 | return owner; 35 | } 36 | 37 | public RcovFileResult getResult() { 38 | return result; 39 | } 40 | 41 | public String getDisplayName() { 42 | return "Rcov report for: " + result.getName(); 43 | } 44 | 45 | public String loadSourceCode() { 46 | try { 47 | RcovParser parser = new RcovParser(owner.getRootDir()); 48 | return parser.parseSource(getResult().getLinkPath()); 49 | } catch (IOException e) { 50 | LOGGER.log(SEVERE, Messages.RcovFileDetail_ParseError(result.getName()), e); 51 | return Messages.RcovFileDetail_ParseErrorHtml(e.getMessage()); 52 | } catch (ParserException e) { 53 | LOGGER.log(SEVERE, Messages.RcovFileDetail_ParseError(result.getName()), e); 54 | return Messages.RcovFileDetail_ParseErrorHtml(e.getMessage()); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/rcov/model/RcovFileResult.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.rcov.model; 2 | 3 | import java.io.ObjectStreamException; 4 | import java.io.Serializable; 5 | 6 | public class RcovFileResult extends RcovAbstractResult implements Serializable { 7 | 8 | static final long serialVersionUID = 7875682204781173769L; 9 | 10 | private String name; 11 | private String href; 12 | @Deprecated 13 | private transient String sourceCode; 14 | 15 | public String getName() { 16 | return name; 17 | } 18 | public void setName(String name) { 19 | this.name = name; 20 | } 21 | public String getHref() { 22 | return href; 23 | } 24 | public void setHref(String href) { 25 | this.href = href; 26 | } 27 | 28 | public String getLinkPath() { 29 | if (href == null || href.endsWith(".html")) { 30 | return href; 31 | } else { 32 | return href + ".html"; 33 | } 34 | } 35 | 36 | // Ensure that the sourceCode field is discarded when loading old builds 37 | private Object readResolve() throws ObjectStreamException { 38 | this.sourceCode = null; 39 | return this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/rcov/model/RcovResult.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.rcov.model; 2 | 3 | import org.jvnet.localizer.Localizable; 4 | 5 | import java.util.Collection; 6 | import java.util.LinkedHashSet; 7 | 8 | public class RcovResult extends RcovAbstractResult { 9 | 10 | private Collection files = new LinkedHashSet(); 11 | 12 | public RcovFileResult getFile(String href) { 13 | RcovFileResult file = null; 14 | 15 | for (RcovFileResult it : files) { 16 | if (it.getHref().equalsIgnoreCase(href)) { 17 | file = it; 18 | break; 19 | } 20 | } 21 | 22 | return file; 23 | } 24 | 25 | public Collection getFiles() { 26 | return files; 27 | } 28 | 29 | public void setFiles(Collection files) { 30 | this.files = files; 31 | } 32 | 33 | public void addFile(RcovFileResult file) { 34 | this.files.add(file); 35 | } 36 | 37 | public Float getRatioFloat(Targets metric) { 38 | return metric.equals(Targets.TOTAL_COVERAGE)? getTotalCoverageFloat():getCodeCoverageFloat(); 39 | } 40 | 41 | public String getRatio(Targets metric) { 42 | return metric.equals(Targets.TOTAL_COVERAGE)? getTotalCoverage():getCodeCoverage(); 43 | } 44 | 45 | public Localizable getHealthDescription(Targets metric) { 46 | return Messages._RcovResult_HealthDescription(metric.getName(), getRatio(metric), getRatioFloat(metric)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/rubyMetrics/rcov/model/Targets.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.rcov.model; 2 | 3 | public enum Targets { 4 | TOTAL_COVERAGE, CODE_COVERAGE; 5 | 6 | public String getName() { 7 | String name = this.toString().toLowerCase().replaceAll("_", " "); 8 | return name.substring(0, 1).toUpperCase() + name.substring(1); 9 | } 10 | 11 | public static Targets resolve(String name) { 12 | return Targets.valueOf(name.toUpperCase().replaceAll(" ", "_")); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/flog/FlogBuildAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 |

Flog report

10 | 11 | 12 | 13 | 14 | 15 |
16 | Total score: ${it.results.total} 17 | Average score: ${it.results.average} 18 |
19 | 20 |
21 | 22 |
${fileResult.value.total}: ${fileResult.key}
23 |
24 |
25 | 26 |
${methodResult.score}: ${methodResult.name}
27 |
28 |
    29 | 30 |
  • ${operatorResult.value}: ${operatorResult.key}
  • 31 |
    32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 40 |
41 | 42 | 43 |
44 |
45 | 46 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/flog/FlogProjectAction/floatingBox.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/flog/FlogProjectAction/nodata.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 |

No valid coverage data available

8 |

9 | Tracking and trending code coverage works best when like is compared with like. In this regard it is 10 | best to only track builds when all unit tests are passing. 11 |

12 |

13 | This plugin will not report code coverage until there is at least one stable build. 14 |

15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/flog/FlogPublisher/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/railsNotes/RailsNotesBuildAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 |

Annotations (Rails notes) report

10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
${header}
${metric.key}${values.value}
34 | 35 |

Output

36 |
37 |
${it.results.output}
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/railsNotes/RailsNotesProjectAction/floatingBox.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/railsNotes/RailsNotesProjectAction/nodata.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 |

No annotation data available

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/railsNotes/RailsNotesPublisher/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/railsStats/RailsStatsBuildAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 |

Rails stats report

10 | 11 | 12 |

Ratios

13 | 14 |
15 |

Classes

16 | 17 |
18 |

Lines of Code

19 | 20 |
21 |

Stats

22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
${header}
${metric.key}${values.value}
43 | 44 |
45 | Code LOC: ${it.results.codeLocSummary} 46 | Test LOC: ${it.results.testLocSummary} 47 | Code to Test Ratio: ${it.results.codeToTestRatio} 48 |
49 |
50 |
51 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/railsStats/RailsStatsProjectAction/floatingBox.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/railsStats/RailsStatsProjectAction/nodata.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 |

No valid coverage data available

8 |

9 | Tracking and trending code coverage works best when like is compared with like. In this regard it is 10 | best to only track builds when all unit tests are passing. 11 |

12 |

13 | This plugin will not report code coverage until there is at least one stable build. 14 |

15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/railsStats/RailsStatsPublisher/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/rcov/RcovBuildAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 |

Rcov coverage report

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 48 | 50 | 51 | 52 | 53 | 54 |
NameTotal linesLines of codeTotal coverageCode coverage
TOTAL${it.results.totalLines}${it.results.codeLines}
${file.name}${file.totalLines}${file.codeLines}
55 |
56 |
57 |
58 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/rcov/RcovProjectAction/floatingBox.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/rcov/RcovProjectAction/nodata.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 |

No valid coverage data available

8 |

9 | Tracking and trending code coverage works best when like is compared with like. In this regard it is 10 | best to only track builds when all unit tests are passing. 11 |

12 |

13 | This plugin will not report code coverage until there is at least one stable build. 14 |

15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/rcov/RcovPublisher/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 32 | 36 | 39 | 43 | 46 | 47 | 48 | 49 |
${inst.metric.name} 26 | 100% 28 | 30 | 31 | 33 | 0% 35 | 37 | 38 | 40 | 0% 42 | 44 | 45 |
50 |
51 | 52 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/rcov/model/Messages.properties: -------------------------------------------------------------------------------- 1 | RcovResult.HealthDescription=Rcov coverage: {0} {1}({2}) 2 | RcovFileDetail.ParseError=Unable to load source code for {0} 3 | RcovFileDetail.ParseErrorHtml=
Unable to load source code: {0}
4 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/rcov/model/RcovFileDetail/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 |

${it.displayName}

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 32 | 33 | 34 | 35 |
NameTotal linesLines of codeTotal coverageCode coverage
${it.result.name}${it.result.totalLines}${it.result.codeLines}
36 | 37 |
38 | 39 |
40 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/tags/floatingBox.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 |
7 | ${attrs.title} 8 |
9 | 10 | 11 |
12 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/tags/tableGraph.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | 18 |
${attrs.coverage} 10 | 11 | 12 | 15 |
13 | 14 |
16 |
19 | 20 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/rubyMetrics/tags/taglib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/rubymetrics-plugin/6d7c17f8c0a161d0ed8d323b17562e11458008d6/src/main/resources/hudson/plugins/rubyMetrics/tags/taglib -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | This plugin integrates a bunch of ruby coverage tools (Rcov, Saikuro, Rails stats...) 4 | to Jenkins. Currently it just supports Rcov reports. 5 |
6 | -------------------------------------------------------------------------------- /src/main/ruby/generator.rb: -------------------------------------------------------------------------------- 1 | # Script to generate the skeleton for a rubyMetrics publisher 2 | require 'rubygems' 3 | require 'erubis' 4 | require 'fileutils' 5 | 6 | # grabbed from active_support 7 | def camelize(string, first_up = true) 8 | if first_up 9 | string.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } 10 | else 11 | string[0].chr.downcase + camelize(string)[1..-1] 12 | end 13 | end 14 | 15 | def template(name) 16 | Erubis::Eruby.new(File.read(File.join(File.dirname(__FILE__), 'templates', name))) 17 | end 18 | 19 | def ask_empty(message) 20 | begin 21 | print message 22 | answer = gets.chomp 23 | end while answer.empty? 24 | answer 25 | end 26 | 27 | def ask_bool(message) 28 | begin 29 | print message 30 | answer = gets.chomp 31 | answer = 'N' if answer.empty? 32 | end while !(answer =~ /[NnSs]/) 33 | answer.downcase == 's' 34 | end 35 | 36 | def write(path, content) 37 | File.open(path, "w") do |file| 38 | file.write(content) 39 | end 40 | end 41 | 42 | real_name = ask_empty('the name of the tool to add support for is: ') 43 | 44 | tool_name = real_name.gsub(/\s+/, '_') 45 | camelized_name = camelize(tool_name) 46 | package_name = camelize(tool_name, false) 47 | 48 | puts "generating directory structure..." 49 | 50 | java_base_directory = File.join(File.dirname(__FILE__), '..', 'java', 'hudson', 'plugins', 'rubyMetrics', package_name) 51 | java_model_directory = File.join(java_base_directory, 'model') 52 | FileUtils.mkdir_p java_base_directory 53 | FileUtils.mkdir_p java_model_directory 54 | 55 | jelly_directory = File.join(File.dirname(__FILE__), '..', 'resources', 'hudson', 'plugins', 'rubyMetrics', package_name) 56 | jelly_paths = {} 57 | ["#{camelized_name}Publisher", "#{camelized_name}BuildAction", "#{camelized_name}ProjectAction"].each do |path| 58 | jelly_paths[path] = FileUtils.mkdir_p File.join(jelly_directory, path) 59 | end 60 | 61 | options = { 62 | :real_name => real_name, 63 | :tool_name => tool_name, 64 | :camelized_name => camelized_name, 65 | :package_name => package_name 66 | } 67 | publisher_template = 'publisher.eruby' 68 | 69 | rails_task = ask_bool('are you going to parse the output of a rails task? (N/s) ') 70 | if (rails_task) 71 | options[:rake_task] = ask_empty("the rake task that I'm going to run is: ") 72 | publisher_template = 'rails_task_publisher.eruby' 73 | else 74 | options[:html_report] = ask_bool('are you going to publish an html parsed? (N/s) ') 75 | 76 | options[:superclass_name] = options[:html_report] ? 'HtmlPublisher' : 'AbstractRubyMetricsPublisher' 77 | 78 | options[:health_report] = ask_bool('are you going to use health thresholds? (N/s) ') 79 | end 80 | 81 | puts "generating skeleton for #{camelized_name}Publisher..." 82 | publisher = template(publisher_template).evaluate(options) 83 | write("#{java_base_directory}/#{camelized_name}Publisher.java", publisher) 84 | 85 | puts "generating skeleton for #{camelized_name}BuildAction..." 86 | build_action = template('build_action.eruby').evaluate(options) 87 | write("#{java_base_directory}/#{camelized_name}BuildAction.java", build_action) 88 | 89 | puts "generating skeleton for #{camelized_name}ProjectAction..." 90 | project_action = template('project_action.eruby').evaluate(options) 91 | write("#{java_base_directory}/#{camelized_name}ProjectAction.java", project_action) 92 | 93 | puts "generating skeleton for #{camelized_name}BuildResults..." 94 | build_results = template('build_results.eruby').evaluate(options) 95 | write("#{java_model_directory}/#{camelized_name}BuildResults.java", build_results) 96 | 97 | puts "generating publisher configuration view..." 98 | options[:config_title] = ask_empty('the title of the configuration option is: ') 99 | print 'the description of the configuration option is: ' 100 | options[:config_description] = gets.chomp 101 | publisher_view = template('jelly/publisher_config.eruby').evaluate(options) 102 | write(File.join(jelly_paths["#{camelized_name}Publisher"], 'config.jelly'), publisher_view) 103 | 104 | puts "generating build action view..." 105 | build_view = template('jelly/build_action.eruby').evaluate(options) 106 | write(File.join(jelly_paths["#{camelized_name}BuildAction"], 'index.jelly'), build_view) 107 | 108 | puts "generating project action view..." 109 | project_box_view = template('jelly/project_action_box.eruby').evaluate(options) 110 | write(File.join(jelly_paths["#{camelized_name}ProjectAction"], 'floatingBox.jelly'), project_box_view) 111 | 112 | FileUtils.cp File.join(File.dirname(__FILE__), 'templates', 'jelly/project_action_no_data.eruby'), 113 | File.join(jelly_paths["#{camelized_name}ProjectAction"], 'nodata.jelly') 114 | 115 | puts "end." 116 | -------------------------------------------------------------------------------- /src/main/ruby/templates/build_action.eruby: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.<%= @package_name %>; 2 | 3 | import hudson.model.AbstractBuild; 4 | import hudson.model.HealthReport; 5 | import hudson.plugins.rubyMetrics.AbstractRubyMetricsBuildAction; 6 | import hudson.plugins.rubyMetrics.<%= @package_name %>.model.<%= @camelized_name %>BuildResults; 7 | import hudson.util.ChartUtil; 8 | import hudson.util.DataSetBuilder; 9 | import hudson.util.ChartUtil.NumberOnlyBuildLabel; 10 | 11 | import java.math.BigDecimal; 12 | import java.math.MathContext; 13 | import java.util.Map; 14 | 15 | import org.jfree.chart.axis.NumberAxis; 16 | import org.jfree.chart.plot.CategoryPlot; 17 | 18 | public class <%= @camelized_name %>BuildAction extends AbstractRubyMetricsBuildAction { 19 | 20 | private final <%= @camelized_name %>BuildResults results; 21 | 22 | public <%= @camelized_name %>BuildAction(AbstractBuild owner, <%= @camelized_name %>BuildResults results) { 23 | super(owner); 24 | this.results = results; 25 | } 26 | 27 | @Override 28 | protected DataSetBuilder getDataSetBuilder() { 29 | DataSetBuilder dsb = new DataSetBuilder(); 30 | 31 | ChartUtil.NumberOnlyBuildLabel label = new ChartUtil.NumberOnlyBuildLabel(action.owner); 32 | 33 | /* add data to the graph, i.e: */ 34 | /* dsb.add(results.getTotal(), "Total score", label);*/ 35 | 36 | return dsb; 37 | } 38 | 39 | public <%= @camelized_name %>BuildResults getResults() { 40 | return results; 41 | } 42 | 43 | public String getDisplayName() { 44 | return "<%= @camelized_name %> report"; 45 | } 46 | 47 | public String getUrlName() { 48 | return "<%= @tool_name %>"; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/ruby/templates/build_results.eruby: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.<%= @package_name %>.model; 2 | 3 | public class <%= @camelized_name %>BuildResults { 4 | /* model to transport data */ 5 | } 6 | -------------------------------------------------------------------------------- /src/main/ruby/templates/jelly/build_action.eruby: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 |

<%= @real_name %> report

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 | 21 | -------------------------------------------------------------------------------- /src/main/ruby/templates/jelly/project_action_box.eruby: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/ruby/templates/jelly/project_action_no_data.eruby: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 |

No valid coverage data available

7 |

8 | Tracking and trending code coverage works best when like is compared with like. In this regard it is 9 | best to only track builds when all unit tests are passing. 10 |

11 |

12 | This plugin will not report code coverage until there is at least one stable build. 13 |

14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /src/main/ruby/templates/jelly/publisher_config.eruby: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | }" /> 6 | 7 | 8 | <% if @health_report -%> 9 | 10 | 11 | 12 | 43 | <% end -%> 44 | 45 | -------------------------------------------------------------------------------- /src/main/ruby/templates/project_action.eruby: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.<%= @package_name %>; 2 | 3 | import hudson.model.AbstractProject; 4 | import hudson.plugins.rubyMetrics.AbstractRubyMetricsProjectAction; 5 | 6 | public class <%= @camelized_name %>ProjectAction<<%= @camelized_name %>BuildAction> extends AbstractRubyMetricsProjectAction { 7 | 8 | public <%= @camelized_name %>ProjectAction(AbstractProject project) { 9 | super(project); 10 | } 11 | 12 | @Override 13 | protected Class getBuildActionClass() { 14 | return hudson.plugins.rubyMetrics.<%= @package_name %>.<%= @camelized_name %>BuildAction.class; 15 | } 16 | 17 | public String getDisplayName() { 18 | return "<%= @camelized_name %> report"; 19 | } 20 | 21 | public String getUrlName() { 22 | return "<%= @tool_name %>"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/ruby/templates/publisher.eruby: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.<%= @package_name %>; 2 | 3 | import hudson.Extension; 4 | import hudson.Launcher; 5 | import hudson.model.AbstractBuild; 6 | import hudson.model.AbstractProject; 7 | import hudson.model.Action; 8 | import hudson.model.BuildListener; 9 | import hudson.plugins.rubyMetrics.<%= @superclass_name %>; 10 | import hudson.plugins.rubyMetrics.<%= @package_name %>.model.<%= @camelized_name %>BuildResults; 11 | import hudson.tasks.BuildStepDescriptor; 12 | import hudson.tasks.Publisher; 13 | 14 | import java.io.IOException; 15 | 16 | import org.kohsuke.stapler.DataBoundConstructor; 17 | 18 | public class <%= @camelized_name %>Publisher extends <%= @superclass_name %> { 19 | 20 | <% unless @html_report -%> 21 | private final String reportConfig; 22 | <% end %> 23 | 24 | @DataBoundConstructor 25 | public <%= @camelized_name %>Publisher(String reportConfig) { 26 | <% if @html_report -%> 27 | this.reportDir = reportConfig; 28 | <% else -%> 29 | this.reportConfig = reportConfig; 30 | <% end -%> 31 | } 32 | 33 | <% unless @html_report -%> 34 | public String getReportConfig() { 35 | return reportConfig; 36 | } 37 | <% end -%> 38 | 39 | @Override 40 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { 41 | /* do something */ 42 | 43 | /* publish build results */ 44 | /* 45 | <%= @camelized_name %>BuildResults buildResults = new <%= @camelized_name 46 | %>BuildResults(); 47 | 48 | <%= @camelized_name %>BuildAction action = new <%= @camelized_name %>BuildAction(build, buildResults); 49 | build.getActions().add(action); 50 | */ 51 | 52 | return true; 53 | } 54 | 55 | @Override 56 | public Action getProjectAction(AbstractProject project) { 57 | return new <%= @camelized_name %>ProjectAction<<%= @camelized_name %>BuildAction>(project); 58 | } 59 | 60 | @Override 61 | public BuildStepDescriptor getDescriptor() { 62 | return DESCRIPTOR; 63 | } 64 | 65 | @Extension 66 | public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); 67 | 68 | public static final class DescriptorImpl extends BuildStepDescriptor { 69 | protected DescriptorImpl() { 70 | super(<%= @camelized_name %>Publisher.class); 71 | } 72 | 73 | public String getToolShortName() { 74 | return "<%= @tool_name %>"; 75 | } 76 | 77 | @Override 78 | public String getDisplayName() { 79 | return "Publish <%= @camelized_name %> report"; 80 | } 81 | 82 | @Override 83 | public boolean isApplicable(Class jobType) { 84 | return true; 85 | } 86 | 87 | <% if @health_report -%> 88 | @Override 89 | public SaikuroPublisher newInstance(StaplerRequest req, JSONObject formData) throws FormException { 90 | return req.bindParameters(<%= @camelized_name %>Publisher.class, "<%= 91 | @tool_name %>."); 92 | } 93 | <% end%> 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /src/main/ruby/templates/rails_task_publisher.eruby: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.<%= @package_name %>; 2 | 3 | import hudson.Extension; 4 | import hudson.model.AbstractBuild; 5 | import hudson.model.AbstractProject; 6 | import hudson.model.Action; 7 | import hudson.plugins.rake.Rake; 8 | import hudson.plugins.rake.RubyInstallation; 9 | import hudson.plugins.rubyMetrics.AbstractRailsTaskPublisher; 10 | import hudson.plugins.rubyMetrics.<%= @package_name %>.model.<%= @camelized_name %>BuildResults; 11 | import hudson.tasks.BuildStepDescriptor; 12 | import hudson.tasks.Publisher; 13 | 14 | import java.io.ByteArrayOutputStream; 15 | 16 | import org.kohsuke.stapler.DataBoundConstructor; 17 | 18 | @SuppressWarnings("unchecked") 19 | public class <%= @camelized_name %>Publisher extends AbstractRailsTaskPublisher { 20 | 21 | @DataBoundConstructor 22 | public <%= @camelized_name %>Publisher(String rakeInstallation, String rakeWorkingDir) { 23 | super(rakeInstallation, rakeWorkingDir, <%= @rake_task %>); 24 | } 25 | 26 | protected void buildAction(ByteArrayOutputStream out, AbstractBuild build) { 27 | final <%= @camelized_name %>Parser parser = new <%= @camelized_name %>Parser(); 28 | <%= @camelized_name %>BuildResults results = parser.parse(out); 29 | 30 | RailsStatsBuildAction action = new RailsStatsBuildAction(build, results); 31 | build.getActions().add(action); 32 | } 33 | 34 | @Override 35 | public Action getProjectAction(AbstractProject project) { 36 | return new <%= @camelized_name %>ProjectAction(project); 37 | } 38 | 39 | @Extension 40 | public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); 41 | 42 | public static final class DescriptorImpl extends BuildStepDescriptor { 43 | 44 | protected DescriptorImpl() { 45 | super(<%= @camelized_name %>Publisher.class); 46 | } 47 | 48 | @Override 49 | public String getDisplayName() { 50 | return "Publish <%= @real_name %> report"; 51 | } 52 | 53 | public RubyInstallation[] getRakeInstallations() { 54 | return Rake.DESCRIPTOR.getInstallations(); 55 | } 56 | 57 | @Override 58 | public boolean isApplicable(Class jobType) { 59 | return true; 60 | } 61 | 62 | } 63 | 64 | @Override 65 | public BuildStepDescriptor getDescriptor() { 66 | return DESCRIPTOR; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/webapp/css/style.css: -------------------------------------------------------------------------------- 1 | span.cross-ref-title { 2 | font-size: 140%; 3 | } 4 | span.cross-ref a { 5 | text-decoration: none; 6 | } 7 | span.cross-ref { 8 | background-color:#f3f7fa; 9 | border: 1px dashed #333; 10 | margin: 1em; 11 | padding: 0.5em; 12 | overflow: hidden; 13 | } 14 | a.crossref-toggle { 15 | text-decoration: none; 16 | } 17 | span.marked0 { 18 | background-color: rgb(185, 210, 200); 19 | display: block; 20 | } 21 | span.marked1 { 22 | background-color: rgb(190, 215, 205); 23 | display: block; 24 | } 25 | span.inferred0 { 26 | background-color: rgb(175, 200, 200); 27 | display: block; 28 | } 29 | span.inferred1 { 30 | background-color: rgb(180, 205, 205); 31 | display: block; 32 | } 33 | span.uncovered0 { 34 | background-color: rgb(225, 110, 110); 35 | display: block; 36 | } 37 | span.uncovered1 { 38 | background-color: rgb(235, 120, 120); 39 | display: block; 40 | } 41 | span.overview { 42 | border-bottom: 8px solid black; 43 | } 44 | div.overview { 45 | border-bottom: 8px solid black; 46 | } 47 | body { 48 | font-family: verdana, arial, helvetica; 49 | } 50 | div.footer { 51 | font-size: 68%; 52 | margin-top: 1.5em; 53 | } 54 | h1, h2, h3, h4, h5, h6 { 55 | margin-bottom: 0.5em; 56 | } 57 | h5 { 58 | margin-top: 0.5em; 59 | } 60 | .hidden { 61 | display: none; 62 | } 63 | div.separator { 64 | height: 10px; 65 | } 66 | /* Commented out for better readability, esp. on IE */ 67 | /* 68 | table tr td, table tr th { 69 | font-size: 68%; 70 | } 71 | td.value table tr td { 72 | font-size: 11px; 73 | } 74 | */ 75 | td.coverage_total { 76 | padding-right:1em; 77 | } 78 | 79 | table.percent_graph { 80 | height: 12px; 81 | border: #808080 1px solid; 82 | empty-cells: show; 83 | } 84 | table.percent_graph td.covered { 85 | height: 10px; 86 | background: #00f000; 87 | } 88 | table.percent_graph td.uncovered { 89 | height: 10px; 90 | background: #e00000; 91 | } 92 | table.percent_graph td.NA { 93 | height: 10px; 94 | background: #eaeaea; 95 | } 96 | table.report { 97 | margin-top: 1em; 98 | border-collapse: collapse; 99 | width: 100%; 100 | } 101 | table.report td.heading { 102 | background: #dcecff; 103 | border: #d0d0d0 1px solid; 104 | font-weight: bold; 105 | text-align: center; 106 | } 107 | table.report td.heading:hover { 108 | background: #c0ffc0; 109 | } 110 | table.report td.text { 111 | border: #d0d0d0 1px solid; 112 | } 113 | table.report td.value, 114 | table.report td.lines_total, 115 | table.report td.lines_code { 116 | text-align: right; 117 | border: #d0d0d0 1px solid; 118 | } 119 | table.report tr.light { 120 | background-color: rgb(240, 240, 245); 121 | } 122 | table.report tr.dark { 123 | background-color: rgb(230, 230, 235); 124 | } 125 | 126 | div#summary { 127 | margin-top: 2em; 128 | } 129 | 130 | div#summary span { 131 | padding-right: 2.5em; 132 | } 133 | 134 | pre, code { 135 | color: #000000; 136 | font-family: "Bitstream Vera Sans Mono","Monaco","Courier New",monospace; 137 | font-size: 95%; 138 | line-height: 1.3em; 139 | margin-top: 0; 140 | margin-bottom: 0; 141 | padding: 0; 142 | word-wrap: break-word; 143 | } 144 | 145 | table.details { 146 | margin-top: 1em; 147 | border-collapse: collapse; 148 | width: 100%; 149 | border: 1px solid #666666; 150 | } 151 | 152 | table.details tr { 153 | line-height: 1.75em; 154 | } 155 | 156 | table.details td { 157 | padding: .25em; 158 | } 159 | 160 | span.inferred, span.inferred1, span.marked, span.marked1, span.uncovered, span.uncovered1 { 161 | display: block; 162 | padding: .25em; 163 | } 164 | 165 | tr.inferred td, span.inferred { 166 | background-color: #e0dedb; 167 | } 168 | 169 | tr.inferred1 td, span.inferred1 { 170 | background-color: #e0dedb; 171 | } 172 | 173 | tr.marked td, span.marked, span.marked1 { 174 | background-color: #bed2be; 175 | } 176 | 177 | tr.uncovered td, span.uncovered { 178 | background-color: #ce8b8c; 179 | } 180 | 181 | tr.uncovered1 td, span.uncovered1 { 182 | background-color: #ce8b8c; 183 | } 184 | 185 | div.key { 186 | border: 1px solid #666666; 187 | margin: 1em 0em; 188 | } 189 | 190 | /* Flog accordion */ 191 | 192 | .accordion { 193 | border: 1px solid #aaa; 194 | padding: 4px; 195 | margin: 1em; 196 | background: #fff; 197 | } 198 | 199 | .accordion dt { 200 | background: #3a6ba9; 201 | color: #fff; 202 | font-weight: bold; 203 | padding: 3px 8px; 204 | } 205 | 206 | .accordion dt.selected { 207 | background: #fcaf3e; 208 | } 209 | 210 | .accordion dt:hover, .accordion dt.over { 211 | text-decoration: underline; 212 | cursor: pointer; 213 | cursor: hand; 214 | } 215 | 216 | .accordion dd { 217 | display: none; 218 | border: 1px solid #aaa; 219 | padding: 4px; 220 | overflow: hidden; 221 | } 222 | 223 | .accordion dd.open { 224 | display: block; 225 | } 226 | 227 | .accordion dd.getHeight { 228 | display: block; 229 | } 230 | -------------------------------------------------------------------------------- /src/main/webapp/images/24x24/tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/rubymetrics-plugin/6d7c17f8c0a161d0ed8d323b17562e11458008d6/src/main/webapp/images/24x24/tab.png -------------------------------------------------------------------------------- /src/main/webapp/js/flog.accordion.js: -------------------------------------------------------------------------------- 1 | var Dom = YAHOO.util.Dom; 2 | var Event = YAHOO.util.Event; 3 | 4 | YAHOO.namespace("flog"); 5 | 6 | YAHOO.flog.accordion = { 7 | init: function() { 8 | var accordion = document.getElementById("flog-accordion"); 9 | var headers = accordion.getElementsByTagName("dt"); 10 | 11 | Event.addListener(headers, "click", this.click); 12 | }, 13 | 14 | click: function(e) { 15 | var header = this; 16 | 17 | if (Dom.hasClass(header, "selected")) { 18 | YAHOO.flog.accordion.collapse(header); 19 | } else { 20 | YAHOO.flog.accordion.expand(header); 21 | } 22 | }, 23 | 24 | collapse: function(header) { 25 | Dom.removeClass(header,"selected"); 26 | Dom.removeClass(Dom.getNextSibling(header), "open"); 27 | }, 28 | 29 | expand: function(header) { 30 | Dom.addClass(header,"selected"); 31 | Dom.addClass(Dom.getNextSibling(header), "open"); 32 | } 33 | } 34 | 35 | Event.on(window,"load", YAHOO.flog.accordion.init()); 36 | -------------------------------------------------------------------------------- /src/main/webapp/railsNotesHelp.html: -------------------------------------------------------------------------------- 1 | Run Rake notes task and build a nice report with a trend graph. You have to select the rake version you want to use. -------------------------------------------------------------------------------- /src/main/webapp/railsStatsHelp.html: -------------------------------------------------------------------------------- 1 | Run Rake stats task and build a nice report with a trend graph. You have to select the rake version you want to use. -------------------------------------------------------------------------------- /src/test/java/hudson/plugins/rubyMetrics/flog/FlogExecutorTest.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.flog; 2 | 3 | import hudson.EnvVars; 4 | import hudson.FilePath; 5 | import hudson.Launcher; 6 | import hudson.model.FreeStyleProject; 7 | import hudson.util.ArgumentListBuilder; 8 | import org.junit.BeforeClass; 9 | import org.junit.Test; 10 | import org.jvnet.hudson.test.HudsonTestCase; 11 | 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.File; 14 | import java.io.IOException; 15 | 16 | public class FlogExecutorTest extends HudsonTestCase { 17 | 18 | FlogExecutor flog = new FlogExecutor(); 19 | EnvVars environment = new EnvVars(); 20 | Launcher launcher; 21 | FilePath workspace; 22 | FreeStyleProject project; 23 | 24 | @BeforeClass 25 | public void setup() throws InterruptedException, IOException { 26 | FreeStyleProject project = createFreeStyleProject(); 27 | launcher = super.createLocalLauncher(); 28 | workspace = project.getSomeWorkspace(); 29 | } 30 | 31 | @Test 32 | public void testExecute() throws InterruptedException, IOException { 33 | if (flog.isFlogInstalled(launcher, environment, workspace)) { 34 | assertTrue(flog.execute(new String[]{"/tmp"}, launcher, environment, workspace, project.getRootDir()).isEmpty()); 35 | } 36 | } 37 | 38 | @Test 39 | public void testJoin() throws InterruptedException, IOException { 40 | if (flog.isFlogInstalled(launcher, environment, workspace)) { 41 | ArgumentListBuilder arguments = flog.arguments("-ad", new File("command_line_parser.rb").getAbsolutePath()); 42 | ByteArrayOutputStream out = flog.launch(arguments, launcher, environment, workspace); 43 | assertNotNull(out); 44 | assertTrue(out.toString().contains("CommandLineParser::parse")); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/hudson/plugins/rubyMetrics/flog/FlogParserTest.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.flog; 2 | 3 | import hudson.plugins.rubyMetrics.flog.model.FlogFileResults; 4 | import junit.framework.TestCase; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | 10 | public class FlogParserTest extends TestCase { 11 | 12 | public void testParse() throws IOException { 13 | BufferedReader reader = new BufferedReader( 14 | new InputStreamReader(getClass().getResourceAsStream("flog-results-sample.txt"))); 15 | FlogParser parser = new FlogParser(); 16 | 17 | StringBuilder mock = new StringBuilder(""); 18 | String line; 19 | while ((line = reader.readLine()) != null) { 20 | mock.append(line + "\n"); 21 | if (line == null || line.length() == 0) { 22 | mock.append("\n"); 23 | } 24 | } 25 | 26 | FlogFileResults results = parser.parse("lib/trinidad/command_line_parser.rb", mock.toString()); 27 | assertEquals(80.3f, results.total); 28 | assertEquals(40.2f, results.average); 29 | assertEquals(2, results.getMethodResults().size()); 30 | 31 | assertEquals(79.2f, results.getMethodResults().get(0).score); 32 | assertEquals("CommandLineParser::parse lib/trinidad/command_line_parser.rb:5", results.getMethodResults().get(0).name.replaceAll("\\s+", " ")); 33 | assertFalse(results.getMethodResults().get(0).getOperatorResults().isEmpty()); 34 | assertEquals(1.1f, results.getMethodResults().get(1).score); 35 | assertNotNull(results.getMethodResults().get(0).name); 36 | assertFalse(results.getMethodResults().get(1).getOperatorResults().isEmpty()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/hudson/plugins/rubyMetrics/railsNotes/RailsNotesParserTest.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsNotes; 2 | 3 | import hudson.plugins.rubyMetrics.railsNotes.model.RailsNotesResults; 4 | import junit.framework.TestCase; 5 | 6 | public class RailsNotesParserTest extends TestCase { 7 | public void testParse() throws Exception { 8 | RailsNotesParser parser = new RailsNotesParser(); 9 | 10 | String out = "app/controllers/a_controller.rb:\n" + 11 | " * [ 53] [TODO] do this\n" + 12 | "\n" + 13 | "app/models/b model.rb:\n" + 14 | " * [ 1] [FIXME] [TODO]\n" + // should end up as a FIXME 15 | "\n" + 16 | "app/models/c_model.rb:\n" + 17 | " * [111] [OPTIMIZE]\n" + 18 | " * [222] [TODO]\n" + 19 | "\n" + 20 | "test/unit/b test.rb:\n" + 21 | " * [ 2] [TODO]\n" + 22 | "\n" + 23 | "test/unit/c_test.rb:\n" + 24 | " * [ 5] [FIXME]\n" + 25 | "\n" + 26 | "\n"; 27 | 28 | RailsNotesResults metrics = parser.parse(out); 29 | 30 | assertFalse(metrics.getMetrics().isEmpty()); 31 | assertNotNull(metrics.getOutput()); 32 | assertFalse(metrics.getOutput() == ""); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/hudson/plugins/rubyMetrics/railsStats/RailsStatsParserTest.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.railsStats; 2 | 3 | import hudson.plugins.rubyMetrics.railsStats.model.RailsStatsResults; 4 | import junit.framework.TestCase; 5 | 6 | public class RailsStatsParserTest extends TestCase { 7 | 8 | public void testParse() throws Exception { 9 | RailsStatsParser parser = new RailsStatsParser(); 10 | 11 | String out = "+----------------------+-------+-------+---------+---------+-----+-------+\n" + 12 | "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |\n" + 13 | "+----------------------+-------+-------+---------+---------+-----+-------+\n" + 14 | "| Controllers | 15 | 4 | 1 | 0 | 0 | 0 |\n" + 15 | "| Helpers | 3 | 2 | 0 | 0 | 0 | 0 |\n" + 16 | "| Models | 0 | 0 | 0 | 0 | 0 | 0 |\n" + 17 | "| Libraries | 0 | 0 | 0 | 0 | 0 | 0 |\n" + 18 | "| Integration tests | 0 | 0 | 0 | 0 | 0 | 0 |\n" + 19 | "| Functional tests | 0 | 0 | 0 | 0 | 0 | 0 |\n" + 20 | "| Unit tests | 0 | 0 | 0 | 0 | 0 | 0 |\n" + 21 | "+----------------------+-------+-------+---------+---------+-----+-------+\n" + 22 | "| Total | 18 | 6 | 1 | 0 | 0 | 0 |\n" + 23 | "+----------------------+-------+-------+---------+---------+-----+-------+\n" + 24 | "Code LOC: 6 Test LOC: 0 Code to Test Ratio: 1:0.0"; 25 | 26 | RailsStatsResults metrics = parser.parse(out); 27 | 28 | assertTrue(!metrics.getMetrics().isEmpty()); 29 | assertNotNull(metrics.getCodeLocSummary()); 30 | assertNotNull(metrics.getTestLocSummary()); 31 | assertNotNull(metrics.getCodeToTestRatio()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/hudson/plugins/rubyMetrics/rcov/RcovParserTest.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.rcov; 2 | 3 | import hudson.plugins.rubyMetrics.rcov.model.RcovFileResult; 4 | import hudson.plugins.rubyMetrics.rcov.model.RcovResult; 5 | import junit.framework.TestCase; 6 | 7 | import java.io.File; 8 | import java.io.InputStream; 9 | 10 | /** 11 | * RcovParser test 12 | * 13 | * @author David Calavera 14 | * 15 | */ 16 | public class RcovParserTest extends TestCase { 17 | 18 | public void testParse() throws Exception { 19 | InputStream input = this.getClass().getResourceAsStream("index.html"); 20 | File root = new File(this.getClass().getResource("index.html").toURI()).getParentFile(); 21 | 22 | assertReportIsComplete(root, input); 23 | } 24 | 25 | public void testParseRcov_0_9() throws Exception { 26 | InputStream input = this.getClass().getResourceAsStream("index_0_9.html"); 27 | File root = new File(this.getClass().getResource("index_0_9.html").toURI()).getParentFile(); 28 | 29 | assertReportIsComplete(root, input); 30 | } 31 | 32 | public void testParseSourceNewHtml() throws Exception { 33 | assertSourceIsWellFormed("lib-trinidad-core_ext_rb.html", "", "
"); 34 | } 35 | 36 | public void testParseSourceLegacy() throws Exception { 37 | assertSourceIsWellFormed("lib-algebra_rb.html", "
", "
"); 38 | } 39 | 40 | private void assertSourceIsWellFormed(String href, String start, String end) throws Exception { 41 | File root = new File(this.getClass().getResource(href).toURI()).getParentFile(); 42 | RcovParser parser = new RcovParser(root); 43 | 44 | String source = parser.parseSource(href); 45 | assertNotNull(source); 46 | assertTrue(source.startsWith(start)); 47 | assertTrue(source.endsWith(end)); 48 | } 49 | 50 | private void assertReportIsComplete(File root, InputStream input) throws Exception { 51 | RcovParser parser = new RcovParser(root); 52 | RcovResult result = parser.parse(input); 53 | 54 | assertNotNull(result); 55 | 56 | assertTrue(result.getFiles().size() > 0); 57 | 58 | assertIsAValidNode(result.getTotalCoverage()); 59 | assertIsAValidNode(result.getTotalLines()); 60 | assertIsAValidNode(result.getCodeCoverage()); 61 | assertIsAValidNode(result.getCodeLines()); 62 | 63 | //Check first file 64 | RcovFileResult fileResult = result.getFiles().iterator().next(); 65 | 66 | assertIsAValidNode(fileResult.getTotalCoverage()); 67 | assertIsAValidNode(fileResult.getTotalLines()); 68 | assertIsAValidNode(fileResult.getCodeCoverage()); 69 | assertIsAValidNode(fileResult.getCodeLines()); 70 | } 71 | 72 | private void assertIsAValidNode(String element) { 73 | assertTrue(element.matches("[0-9%.]+")); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/hudson/plugins/rubyMetrics/rcov/model/RcovFileDetailTest.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.rubyMetrics.rcov.model; 2 | 3 | import hudson.FilePath; 4 | import hudson.Launcher; 5 | import hudson.model.AbstractBuild; 6 | import hudson.model.BuildListener; 7 | import hudson.model.FreeStyleBuild; 8 | import hudson.model.FreeStyleProject; 9 | import hudson.plugins.rubyMetrics.rcov.RcovBuildAction; 10 | import hudson.plugins.rubyMetrics.rcov.RcovPublisher; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import org.jvnet.hudson.test.JenkinsRule; 14 | import org.jvnet.hudson.test.TestBuilder; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.util.Collections; 19 | 20 | import static org.junit.Assert.assertNotNull; 21 | import static org.junit.Assert.assertThat; 22 | import static org.junit.matchers.JUnitMatchers.containsString; 23 | 24 | public class RcovFileDetailTest { 25 | private static final String COVERAGE_DIR = "coverage/rcov"; 26 | 27 | @Rule 28 | public final JenkinsRule j = new JenkinsRule(); 29 | 30 | @Test 31 | public void testLoadSourceCode() throws Exception { 32 | File resourceDir = new File(this.getClass().getResource("index.html").toURI()).getParentFile(); 33 | final FilePath resourcePath = new FilePath(resourceDir); 34 | 35 | FreeStyleProject project = j.createFreeStyleProject(); 36 | 37 | // Builder to copy the coverage fixture files into the workspace 38 | project.getBuildersList().add(new TestBuilder() { 39 | public boolean perform(AbstractBuild build, Launcher launcher, 40 | BuildListener listener) throws InterruptedException, IOException { 41 | 42 | FilePath rcovDir = build.getWorkspace().child(COVERAGE_DIR); 43 | rcovDir.mkdirs(); 44 | resourcePath.copyRecursiveTo("*.html", rcovDir); 45 | return true; 46 | } 47 | }); 48 | 49 | // Add the rcov publisher 50 | RcovPublisher publisher = new RcovPublisher(COVERAGE_DIR); 51 | publisher.setTargets(Collections.emptyList()); 52 | project.getPublishersList().add(publisher); 53 | 54 | // Run and wait for the build; assert success 55 | FreeStyleBuild build = j.assertBuildStatusSuccess(project.scheduleBuild2(0)); 56 | 57 | // Assert that the build has an rcov build action attached 58 | RcovBuildAction action = build.getAction(RcovBuildAction.class); 59 | assertNotNull("Missing RcovBuildAction for build " + build, action); 60 | 61 | // Check each detail object to see that it can load its source code 62 | for (RcovFileResult result: action.getResults().getFiles()) { 63 | RcovFileDetail detail = new RcovFileDetail(build, result); 64 | String sourceCode = detail.loadSourceCode(); 65 | assertNotNull("Missing source code for file " + result.getName(), sourceCode); 66 | assertThat("Missing table tag in source code for file " + result.getName(), sourceCode, containsString(" 3000, 8 | :environment => 'development', 9 | :context_path => '/', 10 | :libs_dir => 'lib', 11 | :classes_dir => 'classes', 12 | :config => 'config/tomcat.yml', 13 | :ssl_port => 8443, 14 | :ajp_port => 8009 15 | } 16 | 17 | parser = OptionParser.new do |opts| 18 | opts.banner = 'Trinidad server default options:' 19 | opts.separator '' 20 | 21 | opts.on('-e', '--env ENVIRONMENT', 'Rails environment', 22 | "default: #{default_options[:environment]}") do |v| 23 | default_options[:environment] = v 24 | end 25 | 26 | opts.on('-p', '--port PORT', 'Port to bind to', 27 | "default: #{default_options[:port]}") do |v| 28 | default_options[:port] = v 29 | end 30 | 31 | opts.on('-c', '--context CONTEXT_PATH', 'The application context path', 32 | "default: #{default_options[:context_path]}") do |v| 33 | default_options[:context_path] = v 34 | end 35 | 36 | opts.on('--lib', '--jars LIBS_DIR', 'Directory containing jars used by the application', 37 | "default: #{default_options[:libs_dir]}") do |v| 38 | default_options[:libs_dir] = v 39 | end 40 | 41 | opts.on('--classes', '--classes CLASSES_DIR', 'Directory containing classes used by the application', 42 | "default: #{default_options[:classes_dir]}") do |v| 43 | default_options[:classes_dir] = v 44 | end 45 | 46 | opts.on('-s', '--ssl [SSL_PORT]', 'Enable secure socket layout', 47 | "default port: #{default_options[:ssl_port]}") do |v| 48 | ssl_port = v.nil? ? default_options.delete(:ssl_port) : v.to_i 49 | default_options[:ssl] = {:port => ssl_port} 50 | end 51 | 52 | opts.on('-a', '--ajp [AJP_PORT]', 'Enable ajp connections', 53 | "default port: #{default_options[:ajp_port]}") do |v| 54 | ajp_port = v.nil? ? default_options.delete(:ajp_port) : v.to_i 55 | default_options[:ajp] = {:port => ajp_port} 56 | end 57 | 58 | opts.on('-f', '--config [CONFIG_FILE]', 'Configuration file', 59 | "default: #{default_options[:config]}") do |v| 60 | default_options[:config] = v if v 61 | default_options.deep_merge! YAML.load_file(default_options[:config]) 62 | end 63 | 64 | opts.on('-r', '--rackup [RACKUP_FILE]', 'Rackup configuration file', 65 | 'default: config.ru') do |v| 66 | default_options[:rackup] = v || 'config.ru' 67 | end 68 | 69 | opts.on('--public', '--public DIRECTORY', 'Public directory', 'default: public') do |v| 70 | default_options[:public] = v 71 | end 72 | 73 | opts.on('-v', '--version', 'display the current version') do 74 | puts File.read(File.join(File.dirname(__FILE__), '..', '..', 'VERSION')).chomp 75 | exit 76 | end 77 | 78 | opts.on('-h', '--help', 'display the help') do 79 | puts opts 80 | exit 81 | end 82 | 83 | opts.parse!(ARGV) 84 | end 85 | 86 | default_options 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /src/test/resources/hudson/plugins/rubyMetrics/flog/flog-results-sample.txt: -------------------------------------------------------------------------------- 1 | 80.3: flog total 2 | 40.2: flog/method average 3 | 4 | 79.2: CommandLineParser::parse /Users/david/dev/trinidad/lib/trinidad/command_line_parser.rb:5 5 | 38.7: assignment 6 | 24.3: branch 7 | 16.8: on 8 | 14.8: [] 9 | 3.4: delete 10 | 3.4: to_i 11 | 3.2: nil? 12 | 3.2: puts 13 | 3.2: exit 14 | 2.4: dirname 15 | 2.2: join 16 | 2.0: read 17 | 1.8: load_file 18 | 1.8: chomp 19 | 1.6: deep_merge! 20 | 1.4: separator 21 | 1.4: parse! 22 | 1.2: new 23 | 0.9: lit_fixnum 24 | 25 | 1.1: Trinidad#none 26 | 1.1: require 27 | 28 | -------------------------------------------------------------------------------- /src/test/resources/hudson/plugins/rubyMetrics/rcov/index_0_9.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Octopi C0 Coverage Information - RCov 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

Octopi C0 Coverage Information - RCov

15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | 26 |
27 |
28 | 29 | 34 |
35 |
36 | 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 58 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 76 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 92 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 108 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 124 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 140 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 156 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 172 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 188 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 204 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 220 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 236 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 252 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 268 | 273 | 274 | 275 | 276 |
NameTotal LinesLines of CodeTotal CoverageCode Coverage
TOTAL 910 702
64.62%
54 |
55 |
56 |
57 |
56.27%
59 |
60 |
61 |
62 |
lib/octopi.rb235198
46.81%
72 |
73 |
74 |
75 |
40.40%
77 |
78 |
79 |
80 |
lib/octopi/base.rb11194
89.19%
88 |
89 |
90 |
91 |
87.23%
93 |
94 |
95 |
96 |
lib/octopi/blob.rb2119
38.10%
104 |
105 |
106 |
107 |
31.58%
109 |
110 |
111 |
112 |
lib/octopi/branch.rb1816
44.44%
120 |
121 |
122 |
123 |
37.50%
125 |
126 |
127 |
128 |
lib/octopi/commit.rb6544
63.08%
136 |
137 |
138 |
139 |
47.73%
141 |
142 |
143 |
144 |
lib/octopi/error.rb2320
47.83%
152 |
153 |
154 |
155 |
40.00%
157 |
158 |
159 |
160 |
lib/octopi/file_object.rb1513
53.33%
168 |
169 |
170 |
171 |
46.15%
173 |
174 |
175 |
176 |
lib/octopi/issue.rb10270
82.35%
184 |
185 |
186 |
187 |
74.29%
189 |
190 |
191 |
192 |
lib/octopi/key.rb1815
50.00%
200 |
201 |
202 |
203 |
40.00%
205 |
206 |
207 |
208 |
lib/octopi/repository.rb11177
63.06%
216 |
217 |
218 |
219 |
50.65%
221 |
222 |
223 |
224 |
lib/octopi/resource.rb7562
98.67%
232 |
233 |
234 |
235 |
98.39%
237 |
238 |
239 |
240 |
lib/octopi/tag.rb1715
47.06%
248 |
249 |
250 |
251 |
40.00%
253 |
254 |
255 |
256 |
lib/octopi/user.rb9959
58.59%
264 |
265 |
266 |
267 |
37.29%
269 |
270 |
271 |
272 |
277 |
278 | 279 |

Generated on Fri Sep 11 12:36:42 +0200 2009 with rcov 0.9.0

280 | 281 | 296 | 297 | 298 | 299 | -------------------------------------------------------------------------------- /src/test/resources/hudson/plugins/rubyMetrics/rcov/lib-trinidad-core_ext_rb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | lib/trinidad/core_ext.rb 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Trinidad C0 Coverage Information - RCov

13 |

lib/trinidad/core_ext.rb

14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 38 | 43 | 44 | 45 |
NameTotal LinesLines of CodeTotal CoverageCode Coverage
lib/trinidad/core_ext.rb3322
30.30%
34 |
35 |
36 |
37 |
13.64%
39 |
40 |
41 |
42 |
46 |
47 | 48 |

Key

49 | 50 |
Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.
51 | 52 |

Coverage Details

53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 |
1 Hash.class_eval do
2   # Merges self with another hash, recursively.
3   #
4   # This code was lovingly stolen from some random gem:
5   # http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
6   #
7   # Thanks to whoever made it.
8   def deep_merge(hash)
9     target = dup
10 
11     hash.keys.each do |key|
12       if hash[key].is_a? Hash and self[key].is_a? Hash
13         target[key] = target[key].deep_merge(hash[key])
14         next
15       end
16 
17       target[key] = hash[key]
18     end
19 
20     target
21   end
22   
23   def deep_merge!(second)
24     second.each_pair do |k,v|
25       if self[k].is_a?(Hash) and second[k].is_a?(Hash)
26         self[k].deep_merge!(second[k])
27       else
28         self[k] = second[k]
29       end
30     end
31   end
32 
33 end
257 | 258 |

Generated on Wed Dec 30 22:56:48 +0100 2009 with rcov 0.9.7.1

259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /src/test/resources/hudson/plugins/rubyMetrics/rcov/model/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 32 13 | 19 14 | 100.00% 15 | 100.00% 16 | 17 | 18 | 5 19 | 3 20 | 100.00% 21 | 100.00% 22 | lib/app.rb 23 | lib-app_rb 24 | <table class="details"> <tbody> <tr class="marked"> <td><pre><a name="line2">2</a> require 'app/version'</pre></td> </tr> <tr class="marked"> <td><pre><a name="line3">3</a> require 'app/service'</pre></td> </tr> <tr class="inferred"> <td><pre><a name="line4">4</a> </pre></td> </tr> <tr class="marked"> <td><pre><a name="line5">5</a> module App</pre></td> </tr> <tr class="inferred"> <td><pre><a name="line6">6</a> end</pre></td> </tr> </tbody> </table> 25 | 26 | 27 | 11 28 | 6 29 | 100.00% 30 | 100.00% 31 | lib/app/service.rb 32 | lib-app-service_rb 33 | <table class="details"> <tbody> <tr class="marked"> <td><pre><a name="line2">2</a> module App</pre></td> </tr> <tr class="marked"> <td><pre><a name="line3">3</a> class Service</pre></td> </tr> <tr class="marked"> <td><pre><a name="line4">4</a> def do_it</pre></td> </tr> <tr class="marked"> <td><pre><a name="line5">5</a> &quot;Doing it!&quot;</pre></td> </tr> <tr class="inferred"> <td><pre><a name="line6">6</a> end</pre></td> </tr> <tr class="inferred"> <td><pre><a name="line7">7</a> </pre></td> </tr> <tr class="marked"> <td><pre><a name="line8">8</a> def fail</pre></td> </tr> <tr class="marked"> <td><pre><a name="line9">9</a> raise &quot;Fail!&quot;</pre></td> </tr> <tr class="inferred"> <td><pre><a name="line10">10</a> end</pre></td> </tr> <tr class="inferred"> <td><pre><a name="line11">11</a> end</pre></td> </tr> <tr class="inferred"> <td><pre><a name="line12">12</a> end</pre></td> </tr> </tbody> </table> 34 | 35 | 36 | 3 37 | 2 38 | 100.00% 39 | 100.00% 40 | lib/app/version.rb 41 | lib-app-version_rb 42 | <table class="details"> <tbody> <tr class="marked"> <td><pre><a name="line2">2</a> module App</pre></td> </tr> <tr class="marked"> <td><pre><a name="line3">3</a> VERSION = '1.0.0'</pre></td> </tr> <tr class="inferred"> <td><pre><a name="line4">4</a> end</pre></td> </tr> </tbody> </table> 43 | 44 | 45 | 13 46 | 8 47 | 100.00% 48 | 100.00% 49 | spec/app_spec.rb 50 | spec-app_spec_rb 51 | <table class="details"> <tbody> <tr class="marked"> <td><pre><a name="line2">2</a> require 'app'</pre></td> </tr> <tr class="inferred"> <td><pre><a name="line3">3</a> </pre></td> </tr> <tr class="marked"> <td><pre><a name="line4">4</a> describe App::Service do</pre></td> </tr> <tr class="marked"> <td><pre><a name="line5">5</a> it &quot;returns from do_it&quot; do</pre></td> </tr> <tr class="marked"> <td><pre><a name="line6">6</a> service = App::Service.new</pre></td> </tr> <tr class="marked"> <td><pre><a name="line7">7</a> service.do_it.should_not be_nil </pre></td> </tr> <tr class="inferred"> <td><pre><a name="line8">8</a> end</pre></td> </tr> <tr class="inferred"> <td><pre><a name="line9">9</a> </pre></td> </tr> <tr class="marked"> <td><pre><a name="line10">10</a> it &quot;raises from fail&quot; do</pre></td> </tr> <tr class="marked"> <td><pre><a name="line11">11</a> service = App::Service.new</pre></td> </tr> <tr class="marked"> <td><pre><a name="line12">12</a> expect { service.fail }.to raise_error</pre></td> </tr> <tr class="inferred"> <td><pre><a name="line13">13</a> end</pre></td> </tr> <tr class="inferred"> <td><pre><a name="line14">14</a> end</pre></td> </tr> </tbody> </table> 52 | 53 | 54 | 55 | 56 | 57 | TOTAL_COVERAGE 58 | 80 59 | 0 60 | 0 61 | 62 | 63 | CODE_COVERAGE 64 | 80 65 | 0 66 | 0 67 | 68 | 69 | 70 | 71 | 4 72 | SUCCESS 73 | 690 74 | UTF-8 75 | false 76 | 77 | /Users/mike/proj/rubymetrics-plugin/work/jobs/test/workspace 78 | 1.484 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/test/resources/hudson/plugins/rubyMetrics/rcov/model/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Foo C0 Coverage Information - SimpleCov - RCov style 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

Foo C0 Coverage Information - SimpleCov - RCov style

14 | 15 | 16 | 17 |
18 |
19 | 20 | 24 |
25 |
26 | 27 | 32 |
33 |
34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 56 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 74 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 90 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 106 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 122 | 127 | 128 | 129 | 130 |
NameTotal LinesLines of CodeTotal CoverageCode Coverage
TOTAL3219
100.00%
52 |
53 |
54 |
55 |
100.00%
57 |
58 |
59 |
60 |
lib/app.rb53
100.00%
70 |
71 |
72 |
73 |
100.00%
75 |
76 |
77 |
78 |
lib/app/service.rb116
100.00%
86 |
87 |
88 |
89 |
100.00%
91 |
92 |
93 |
94 |
lib/app/version.rb32
100.00%
102 |
103 |
104 |
105 |
100.00%
107 |
108 |
109 |
110 |
spec/app_spec.rb138
100.00%
118 |
119 |
120 |
121 |
100.00%
123 |
124 |
125 |
126 |
131 |
132 | 133 |

Generated on 2014-02-05 12:55:46 -0800 with SimpleCov-RCov 0.2.3

134 | 135 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /src/test/resources/hudson/plugins/rubyMetrics/rcov/model/lib-app-service_rb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | lib/app/service.rb 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Foo C0 Coverage Information - Simploco - RCov

12 |

lib/app/service.rb

13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | 40 | 41 | 42 |
NameTotal LinesLines of CodeTotal CoverageCode Coverage
lib/app/service.rb116
100.00%
31 |
32 |
33 |
34 |
100.00%
36 |
37 |
38 |
39 |
43 |
44 | 45 |

Key

46 | 47 |
Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.
48 | 49 |

Coverage Details

50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
2 module App
3   class Service
4     def do_it
5       "Doing it!"
6     end
7 
8     def fail
9       raise "Fail!"
10     end
11   end
12 end
88 | 89 |

Generated on 2014-02-05 12:55:46 -0800 with SimpleCov-RCov 0.2.3

90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/test/resources/hudson/plugins/rubyMetrics/rcov/model/lib-app-version_rb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | lib/app/version.rb 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Foo C0 Coverage Information - Simploco - RCov

12 |

lib/app/version.rb

13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | 40 | 41 | 42 |
NameTotal LinesLines of CodeTotal CoverageCode Coverage
lib/app/version.rb32
100.00%
31 |
32 |
33 |
34 |
100.00%
36 |
37 |
38 |
39 |
43 |
44 | 45 |

Key

46 | 47 |
Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.
48 | 49 |

Coverage Details

50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
2 module App
3   VERSION = '1.0.0'
4 end
64 | 65 |

Generated on 2014-02-05 12:55:46 -0800 with SimpleCov-RCov 0.2.3

66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/test/resources/hudson/plugins/rubyMetrics/rcov/model/lib-app_rb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | lib/app.rb 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Foo C0 Coverage Information - Simploco - RCov

12 |

lib/app.rb

13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | 40 | 41 | 42 |
NameTotal LinesLines of CodeTotal CoverageCode Coverage
lib/app.rb53
100.00%
31 |
32 |
33 |
34 |
100.00%
36 |
37 |
38 |
39 |
43 |
44 | 45 |

Key

46 | 47 |
Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.
48 | 49 |

Coverage Details

50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
2 require 'app/version'
3 require 'app/service'
4 
5 module App
6 end
70 | 71 |

Generated on 2014-02-05 12:55:46 -0800 with SimpleCov-RCov 0.2.3

72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/test/resources/hudson/plugins/rubyMetrics/rcov/model/spec-app_spec_rb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spec/app_spec.rb 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Foo C0 Coverage Information - Simploco - RCov

12 |

spec/app_spec.rb

13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | 40 | 41 | 42 |
NameTotal LinesLines of CodeTotal CoverageCode Coverage
spec/app_spec.rb138
100.00%
31 |
32 |
33 |
34 |
100.00%
36 |
37 |
38 |
39 |
43 |
44 | 45 |

Key

46 | 47 |
Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.
48 | 49 |

Coverage Details

50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 |
2 require 'app'
3 
4 describe App::Service do
5   it "returns from do_it" do
6     service = App::Service.new
7     service.do_it.should_not be_nil 
8   end
9 
10   it "raises from fail" do
11     service = App::Service.new
12     expect { service.fail }.to raise_error
13   end
14 end
94 | 95 |

Generated on 2014-02-05 12:55:46 -0800 with SimpleCov-RCov 0.2.3

96 | 97 | 98 | 99 | --------------------------------------------------------------------------------