Java tutorial
/* * The MIT License * * Copyright 2016 Thibault Debatty. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package info.debatty.jinu; import com.mitchellbosecke.pebble.PebbleEngine; import com.mitchellbosecke.pebble.error.PebbleException; import com.mitchellbosecke.pebble.template.PebbleTemplate; import java.awt.Desktop; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; import java.io.Writer; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.nio.charset.Charset; import java.nio.file.Files; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; /** * * @author Thibault Debatty */ public class Case implements Serializable { private static final Logger LOGGER = Logger.getLogger(Case.class.getName()); private String description = ""; private String base_dir = ""; private boolean commit_to_git = true; private int iterations; private final LinkedList<TestInterface> tests = new LinkedList<TestInterface>(); private double[] param_values = null; private int parallelism; private final LinkedList<Listener> listeners = new LinkedList<Listener>(); /** * Initialize case with default parallelism. */ public Case() { int cores = Runtime.getRuntime().availableProcessors(); parallelism = Math.max(1, cores - 2); } /** * Try to commit sources to GIT repository, or not. * @param commit_to_git */ public final void commitToGit(final boolean commit_to_git) { this.commit_to_git = commit_to_git; } /** * Set the base directory, where all test results will be written. * @param base_dir */ public final void setBaseDir(final String base_dir) { this.base_dir = base_dir; } /** * Set the description of the test (will be added to HTML report). * @param description */ public final void setDescription(final String description) { this.description = description; } /** * Set parallelism (default is cores - 2). * Should be set to 1 if your algorithm is already multithreaded, or for * IO intensive tasks. * @param parallelism */ public final void setParallelism(final int parallelism) { this.parallelism = parallelism; } /** * Set the values that will be passed to tests. * @param param_values */ public final void setParamValues(final double[] param_values) { this.param_values = param_values; } /** * Set the number of iterations for running tests. * @param iterations */ public final void setIterations(final int iterations) { this.iterations = iterations; } /** * Add a listener, that will be notified at the end of each iteration. * @param listener */ public final void addListener(final Listener listener) { listeners.add(listener); } /** * Add a test to the case. * @param testclass */ public final void addTest(final Class<? extends TestInterface> testclass) { try { tests.add(testclass.newInstance()); } catch (InstantiationException ex) { Logger.getLogger(Case.class.getName()).log(Level.SEVERE, null, ex); } catch (IllegalAccessException ex) { Logger.getLogger(Case.class.getName()).log(Level.SEVERE, null, ex); } } /** * Run the tests. * @throws java.io.FileNotFoundException if reports cannot be written. * @throws Exception if one of the tests throws an Exception */ public final void run() throws FileNotFoundException, Exception { Date date = new Date(); SimpleDateFormat formater = new SimpleDateFormat("yyyyMMddHHmmss"); SimpleDateFormat day_formater = new SimpleDateFormat("yyyyMMdd"); String time_tag = formater.format(date); String day_tag = day_formater.format(date); String filename = base_dir + day_tag + File.separator + time_tag + ".html"; // Create repository for report if needed File directory = new File(base_dir + day_tag); if (!directory.exists()) { directory.mkdir(); } CaseResult report = createReport(); HashMap<TestAndValue, List<TestResult>> results = new HashMap<TestAndValue, List<TestResult>>(); ExecutorService threadpool = Executors.newFixedThreadPool(parallelism); // Run tests ProgressBar progress = new ProgressBar(iterations); progress.start(); for (int i = 0; i < iterations; i++) { if (param_values == null) { // No param to give to the tests... param_values = new double[] { 0 }; } ArrayList<Future> tasks = new ArrayList<Future>(); for (TestInterface test : tests) { for (double param_value : param_values) { TestAndValue key = new TestAndValue(test, param_value); List<TestResult> resultset = results.get(key); if (resultset == null) { resultset = Collections.synchronizedList(new LinkedList<TestResult>()); results.put(key, resultset); } tasks.add(threadpool.submit(new RunnableTest(test, param_value, resultset))); } } for (Future task : tasks) { task.get(); } progress.update(i + 1); for (Listener listener : listeners) { listener.notify(i); } } report.setResults(results); // Create html report PebbleEngine engine = new PebbleEngine.Builder().build(); Writer writer = new StringWriter(); Map<String, Object> context = new HashMap<String, Object>(); context.put("report", report); try { PebbleTemplate template = engine.getTemplate("templates/report.twig"); template.evaluate(writer, context); } catch (PebbleException ex) { LOGGER.log(Level.WARNING, "Cannot produce html report!", ex); } catch (IOException ex) { LOGGER.log(Level.WARNING, "Cannot produce html report!", ex); } PrintWriter out = new PrintWriter(filename); out.println(writer.toString()); out.close(); launchBrowser(filename); // write data file String data_filename = base_dir + day_tag + File.separator + time_tag + ".dat"; PrintWriter data_writer = new PrintWriter(data_filename); for (List<TestResult> resultlist : results.values()) { for (TestResult result : resultlist) { data_writer.write(result.toCsv() + "\n"); } } data_writer.close(); if (commit_to_git) { commitToGit(time_tag); } threadpool.shutdownNow(); } private CaseResult createReport() { CaseResult report = new CaseResult(); report.setTitle(this.getClass().getName()); report.setTestcase(this); // Try to read the source code of the tests for (TestInterface test : tests) { Class clazz = test.getClass(); String path = "src/main/java/" + clazz.getPackage().getName().replaceAll("\\.", "/"); File source_file = new File(path, clazz.getSimpleName() + ".java"); try { List<String> alllines = Files.readAllLines(source_file.toPath(), Charset.defaultCharset()); StringBuilder builder = new StringBuilder(); for (String line : alllines) { builder.append(line); builder.append("\n"); } report.addSource(test, builder.toString()); } catch (IOException ex) { LOGGER.log(Level.INFO, "Cannot read source of " + clazz.getName(), ex); } } return report; } /** * Write the environemnt variables to a PrintWriter. * @param writer */ public final void writeEnvironment(final PrintWriter writer) { RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); Map<String, String> properties = runtime.getSystemProperties(); ArrayList<String> keys = new ArrayList<String>(properties.keySet()); Collections.sort(keys); writer.write("<h2>Environment</h2>"); writer.write("<pre>"); for (String key : keys) { String value = properties.get(key); writer.printf("%s : %s.\n", key, value); } writer.write("</pre>"); } /** * * @return */ public final double[] getParamValues() { return param_values; } /** * * @return */ public final LinkedList<TestInterface> getTests() { return tests; } /** * * @return */ public final int getIterations() { return iterations; } private void launchBrowser(final String filename) { if (Desktop.isDesktopSupported()) { File file = new File(filename); Desktop desktop = Desktop.getDesktop(); try { desktop.browse(file.toURI()); } catch (IOException e) { LOGGER.log(Level.INFO, "Cannot launch brower", e); } } else { Runtime runtime = Runtime.getRuntime(); try { runtime.exec("xdg-open " + filename); } catch (IOException e) { LOGGER.log(Level.INFO, "Cannot launch brower", e); } } } private void commitToGit(final String time_tag) { try { Repository repo = new FileRepositoryBuilder().findGitDir().build(); Git git = new Git(repo); git.add().addFilepattern(".").call(); git.commit().setAll(true).setMessage("Test case " + time_tag).call(); git.tag().setName("T" + time_tag).call(); } catch (Exception ex) { System.err.println("Could not commit GIT repo"); System.err.println(ex.getMessage()); } } /** * * @return */ public final String getDescription() { return description; } } /** * A wrapper around the task, to be submitted to the executor. * @author tibo */ class RunnableTest implements Runnable { private final TestInterface test; private final double value; private final List<TestResult> resultset; RunnableTest(final TestInterface test, final double value, final List<TestResult> resultset) { this.test = test; this.value = value; this.resultset = resultset; } public void run() { try { long start_time = System.currentTimeMillis(); double[] values = test.run(value); long runtime = System.currentTimeMillis() - start_time; resultset.add(new TestResult(values, runtime, test, value)); System.gc(); System.runFinalization(); } catch (Exception ex) { Logger.getLogger(RunnableTest.class.getName()).log(Level.SEVERE, null, ex); } } }