From bfa7ae73f9c77116ef97876c713e486750c853ae Mon Sep 17 00:00:00 2001 From: phaller Date: Mon, 11 Feb 2008 17:52:15 +0000 Subject: [PATCH] Implemented --show-diff, --show-log and --failed options git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@13947 5e8d7ff9-d8ef-0310-90f0-a4852d11357a --- build.xml | 6 + .../tools/partest/nest/CompileManager.scala | 63 +- .../scala/tools/partest/nest/Diff.java | 894 ++++++++++++++++++ .../scala/tools/partest/nest/DiffPrint.java | 578 +++++++++++ .../tools/partest/nest/FileManager.scala | 50 +- .../scala/tools/partest/nest/NestRunner.scala | 23 +- .../tools/partest/nest/StreamAppender.scala | 32 +- .../scala/tools/partest/nest/Worker.scala | 229 +++-- 8 files changed, 1739 insertions(+), 136 deletions(-) create mode 100644 src/partest/scala/tools/partest/nest/Diff.java create mode 100644 src/partest/scala/tools/partest/nest/DiffPrint.java diff --git a/build.xml b/build.xml index a6fc4a848..e08d5ad99 100644 --- a/build.xml +++ b/build.xml @@ -596,6 +596,12 @@ BUILD QUICK-TEST LAYER /> + PosTestFile(file) + case "neg" => NegTestFile(file) + case "run" => RunTestFile(file) + case "jvm" => JvmTestFile(file) + case "jvm5" => Jvm5TestFile(file) + case "shootout" => ShootoutTestFile(file) + } + test.defineSettings(testSettings) + + val toCompile = List(file.getPath) + try { + (new global.Run) compile toCompile + testRep.printSummary + testRep.writer.flush + testRep.writer.close + } catch { + case e: Exception => + e.printStackTrace() + false + } + !testRep.hasErrors + } def compile(file: File, kind: String): Boolean = { + val testSettings = newSettings + val testRep = newReporter(testSettings) + val global = newGlobal(testSettings, testRep) + val test: TestFile = kind match { case "pos" => PosTestFile(file) case "neg" => NegTestFile(file) @@ -83,18 +113,27 @@ class ReflectiveCompiler extends SimpleCompiler { val sepCompilerClass = sepLoader.loadClass("scala.tools.partest.nest.DirectCompiler") + val sepCompiler = sepCompilerClass.newInstance() + // needed for reflective invocation val fileClass = Class.forName("java.io.File") val stringClass = Class.forName("java.lang.String") val sepCompileMethod = sepCompilerClass.getMethod("compile", Array(fileClass, stringClass)) - val sepCompiler = sepCompilerClass.newInstance() + val sepCompileMethod2 = + sepCompilerClass.getMethod("compile", Array(fileClass, stringClass, fileClass)) def compile(file: File, kind: String): Boolean = { val fileArgs: Array[AnyRef] = Array(file, kind) val res = sepCompileMethod.invoke(sepCompiler, fileArgs).asInstanceOf[java.lang.Boolean] res.booleanValue() } + + def compile(file: File, kind: String, log: File): Boolean = { + val fileArgs: Array[AnyRef] = Array(file, kind, log) + val res = sepCompileMethod2.invoke(sepCompiler, fileArgs).asInstanceOf[java.lang.Boolean] + res.booleanValue() + } } class CompileManager { @@ -106,16 +145,20 @@ class CompileManager { compiler = new ReflectiveCompiler } - def shouldCompile(file: File, kind: String): Boolean = - compiler.compile(file, kind) || { + def shouldCompile(file: File, kind: String): Boolean = { + createSeparateCompiler() + compiler.compile(file, kind) + + /*compiler.compile(file, kind) || { NestUI.verbose("creating new separate compiler") createSeparateCompiler() compiler.compile(file, kind) - } + }*/ + } - def shouldFailCompile(file: File, kind: String): Boolean = { - // always create separate compiler + def shouldFailCompile(file: File, kind: String, log: File): Boolean = { + // always create new separate compiler createSeparateCompiler() - !compiler.compile(file, kind) + !compiler.compile(file, kind, log) } } diff --git a/src/partest/scala/tools/partest/nest/Diff.java b/src/partest/scala/tools/partest/nest/Diff.java new file mode 100644 index 000000000..594a3632e --- /dev/null +++ b/src/partest/scala/tools/partest/nest/Diff.java @@ -0,0 +1,894 @@ +/* + * $Log$ + * Revision 1.1 2003/10/13 16:00:03 michelou + * - Java implementation of GNU diff (GPL licence) + * + * Revision 1.6 2003/03/06 22:51:32 stuart + * Convert to CVS + * + * Revision 1.5 2002/07/19 19:14:40 stuart + * fix reverseScript, make change ctor public, update docs + * + * Revision 1.4 2002/04/09 17:53:39 stuart + * More flexible interface for diff() function. + * + * Revision 1.3 2000/03/03 21:58:03 stuart + * move discard_confusing_lines and shift_boundaries to class file_data + * + * Revision 1.2 2000/03/02 16:37:38 stuart + * Add GPL and copyright + * + */ +//package bmsi.util; +package scala.tools.partest.nest; + +import java.util.Hashtable; + +/** A class to compare vectors of objects. The result of comparison + is a list of change objects which form an + edit script. The objects compared are traditionally lines + of text from two files. Comparison options such as "ignore + whitespace" are implemented by modifying the equals + and hashcode methods for the objects compared. +

+ The basic algorithm is described in:
+ "An O(ND) Difference Algorithm and its Variations", Eugene Myers, + Algorithmica Vol. 1 No. 2, 1986, p 251. +

+ This class outputs different results from GNU diff 1.15 on some + inputs. Our results are actually better (smaller change list, smaller + total size of changes), but it would be nice to know why. Perhaps + there is a memory overwrite bug in GNU diff 1.15. + + @author Stuart D. Gathman, translated from GNU diff 1.15 + Copyright (C) 2000 Business Management Systems, Inc. +

+ This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 1, or (at your option) + any later version. +

+ This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +

+ You should have received a copy of the + GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ + +public class Diff { + + /** Prepare to find differences between two arrays. Each element of + the arrays is translated to an "equivalence number" based on + the result of equals. The original Object arrays + are no longer needed for computing the differences. They will + be needed again later to print the results of the comparison as + an edit script, if desired. + */ + public Diff(Object[] a,Object[] b) { + Hashtable h = new Hashtable(a.length + b.length); + filevec[0] = new file_data(a,h); + filevec[1] = new file_data(b,h); + } + + /** 1 more than the maximum equivalence value used for this or its + sibling file. */ + private int equiv_max = 1; + + /** When set to true, the comparison uses a heuristic to speed it up. + With this heuristic, for files with a constant small density + of changes, the algorithm is linear in the file size. */ + public boolean heuristic = false; + + /** When set to true, the algorithm returns a guarranteed minimal + set of changes. This makes things slower, sometimes much slower. */ + public boolean no_discards = false; + + private int[] xvec, yvec; /* Vectors being compared. */ + private int[] fdiag; /* Vector, indexed by diagonal, containing + the X coordinate of the point furthest + along the given diagonal in the forward + search of the edit matrix. */ + private int[] bdiag; /* Vector, indexed by diagonal, containing + the X coordinate of the point furthest + along the given diagonal in the backward + search of the edit matrix. */ + private int fdiagoff, bdiagoff; + private final file_data[] filevec = new file_data[2]; + private int cost; + + /** Find the midpoint of the shortest edit script for a specified + portion of the two files. + + We scan from the beginnings of the files, and simultaneously from the ends, + doing a breadth-first search through the space of edit-sequence. + When the two searches meet, we have found the midpoint of the shortest + edit sequence. + + The value returned is the number of the diagonal on which the midpoint lies. + The diagonal number equals the number of inserted lines minus the number + of deleted lines (counting only lines before the midpoint). + The edit cost is stored into COST; this is the total number of + lines inserted or deleted (counting only lines before the midpoint). + + This function assumes that the first lines of the specified portions + of the two files do not match, and likewise that the last lines do not + match. The caller must trim matching lines from the beginning and end + of the portions it is going to specify. + + Note that if we return the "wrong" diagonal value, or if + the value of bdiag at that diagonal is "wrong", + the worst this can do is cause suboptimal diff output. + It cannot cause incorrect diff output. */ + + private int diag (int xoff, int xlim, int yoff, int ylim) { + final int[] fd = fdiag; // Give the compiler a chance. + final int[] bd = bdiag; // Additional help for the compiler. + final int[] xv = xvec; // Still more help for the compiler. + final int[] yv = yvec; // And more and more . . . + final int dmin = xoff - ylim; // Minimum valid diagonal. + final int dmax = xlim - yoff; // Maximum valid diagonal. + final int fmid = xoff - yoff; // Center diagonal of top-down search. + final int bmid = xlim - ylim; // Center diagonal of bottom-up search. + int fmin = fmid, fmax = fmid; // Limits of top-down search. + int bmin = bmid, bmax = bmid; // Limits of bottom-up search. + /* True if southeast corner is on an odd + diagonal with respect to the northwest. */ + final boolean odd = (fmid - bmid & 1) != 0; + + fd[fdiagoff + fmid] = xoff; + bd[bdiagoff + bmid] = xlim; + + for (int c = 1;; ++c) + { + int d; /* Active diagonal. */ + boolean big_snake = false; + + /* Extend the top-down search by an edit step in each diagonal. */ + if (fmin > dmin) + fd[fdiagoff + --fmin - 1] = -1; + else + ++fmin; + if (fmax < dmax) + fd[fdiagoff + ++fmax + 1] = -1; + else + --fmax; + for (d = fmax; d >= fmin; d -= 2) + { + int x, y, oldx, tlo = fd[fdiagoff + d - 1], thi = fd[fdiagoff + d + 1]; + + if (tlo >= thi) + x = tlo + 1; + else + x = thi; + oldx = x; + y = x - d; + while (x < xlim && y < ylim && xv[x] == yv[y]) { + ++x; ++y; + } + if (x - oldx > 20) + big_snake = true; + fd[fdiagoff + d] = x; + if (odd && bmin <= d && d <= bmax && bd[bdiagoff + d] <= fd[fdiagoff + d]) + { + cost = 2 * c - 1; + return d; + } + } + + /* Similar extend the bottom-up search. */ + if (bmin > dmin) + bd[bdiagoff + --bmin - 1] = Integer.MAX_VALUE; + else + ++bmin; + if (bmax < dmax) + bd[bdiagoff + ++bmax + 1] = Integer.MAX_VALUE; + else + --bmax; + for (d = bmax; d >= bmin; d -= 2) + { + int x, y, oldx, tlo = bd[bdiagoff + d - 1], thi = bd[bdiagoff + d + 1]; + + if (tlo < thi) + x = tlo; + else + x = thi - 1; + oldx = x; + y = x - d; + while (x > xoff && y > yoff && xv[x - 1] == yv[y - 1]) { + --x; --y; + } + if (oldx - x > 20) + big_snake = true; + bd[bdiagoff + d] = x; + if (!odd && fmin <= d && d <= fmax && bd[bdiagoff + d] <= fd[fdiagoff + d]) + { + cost = 2 * c; + return d; + } + } + + /* Heuristic: check occasionally for a diagonal that has made + lots of progress compared with the edit distance. + If we have any such, find the one that has made the most + progress and return it as if it had succeeded. + + With this heuristic, for files with a constant small density + of changes, the algorithm is linear in the file size. */ + + if (c > 200 && big_snake && heuristic) + { + int best = 0; + int bestpos = -1; + + for (d = fmax; d >= fmin; d -= 2) + { + int dd = d - fmid; + if ((fd[fdiagoff + d] - xoff)*2 - dd > 12 * (c + (dd > 0 ? dd : -dd))) + { + if (fd[fdiagoff + d] * 2 - dd > best + && fd[fdiagoff + d] - xoff > 20 + && fd[fdiagoff + d] - d - yoff > 20) + { + int k; + int x = fd[fdiagoff + d]; + + /* We have a good enough best diagonal; + now insist that it end with a significant snake. */ + for (k = 1; k <= 20; k++) + if (xvec[x - k] != yvec[x - d - k]) + break; + + if (k == 21) + { + best = fd[fdiagoff + d] * 2 - dd; + bestpos = d; + } + } + } + } + if (best > 0) + { + cost = 2 * c - 1; + return bestpos; + } + + best = 0; + for (d = bmax; d >= bmin; d -= 2) + { + int dd = d - bmid; + if ((xlim - bd[bdiagoff + d])*2 + dd > 12 * (c + (dd > 0 ? dd : -dd))) + { + if ((xlim - bd[bdiagoff + d]) * 2 + dd > best + && xlim - bd[bdiagoff + d] > 20 + && ylim - (bd[bdiagoff + d] - d) > 20) + { + /* We have a good enough best diagonal; + now insist that it end with a significant snake. */ + int k; + int x = bd[bdiagoff + d]; + + for (k = 0; k < 20; k++) + if (xvec[x + k] != yvec[x - d + k]) + break; + if (k == 20) + { + best = (xlim - bd[bdiagoff + d]) * 2 + dd; + bestpos = d; + } + } + } + } + if (best > 0) + { + cost = 2 * c - 1; + return bestpos; + } + } + } + } + + /** Compare in detail contiguous subsequences of the two files + which are known, as a whole, to match each other. + + The results are recorded in the vectors filevec[N].changed_flag, by + storing a 1 in the element for each line that is an insertion or deletion. + + The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. + + Note that XLIM, YLIM are exclusive bounds. + All line numbers are origin-0 and discarded lines are not counted. */ + + private void compareseq (int xoff, int xlim, int yoff, int ylim) { + /* Slide down the bottom initial diagonal. */ + while (xoff < xlim && yoff < ylim && xvec[xoff] == yvec[yoff]) { + ++xoff; ++yoff; + } + /* Slide up the top initial diagonal. */ + while (xlim > xoff && ylim > yoff && xvec[xlim - 1] == yvec[ylim - 1]) { + --xlim; --ylim; + } + + /* Handle simple cases. */ + if (xoff == xlim) + while (yoff < ylim) + filevec[1].changed_flag[1+filevec[1].realindexes[yoff++]] = true; + else if (yoff == ylim) + while (xoff < xlim) + filevec[0].changed_flag[1+filevec[0].realindexes[xoff++]] = true; + else + { + /* Find a point of correspondence in the middle of the files. */ + + int d = diag (xoff, xlim, yoff, ylim); + int c = cost; + int f = fdiag[fdiagoff + d]; + int b = bdiag[bdiagoff + d]; + + if (c == 1) + { + /* This should be impossible, because it implies that + one of the two subsequences is empty, + and that case was handled above without calling `diag'. + Let's verify that this is true. */ + throw new IllegalArgumentException("Empty subsequence"); + } + else + { + /* Use that point to split this problem into two subproblems. */ + compareseq (xoff, b, yoff, b - d); + /* This used to use f instead of b, + but that is incorrect! + It is not necessarily the case that diagonal d + has a snake from b to f. */ + compareseq (b, xlim, b - d, ylim); + } + } + } + + /** Discard lines from one file that have no matches in the other file. + */ + + private void discard_confusing_lines() { + filevec[0].discard_confusing_lines(filevec[1]); + filevec[1].discard_confusing_lines(filevec[0]); + } + + private boolean inhibit = false; + + /** Adjust inserts/deletes of blank lines to join changes + as much as possible. + */ + + private void shift_boundaries() { + if (inhibit) + return; + filevec[0].shift_boundaries(filevec[1]); + filevec[1].shift_boundaries(filevec[0]); + } + + public interface ScriptBuilder { + /** Scan the tables of which lines are inserted and deleted, + producing an edit script. + @param changed0 true for lines in first file which do not match 2nd + @param len0 number of lines in first file + @param changed1 true for lines in 2nd file which do not match 1st + @param len1 number of lines in 2nd file + @return a linked list of changes - or null + */ + public change build_script( + boolean[] changed0,int len0, + boolean[] changed1,int len1 + ); + } + + /** Scan the tables of which lines are inserted and deleted, + producing an edit script in reverse order. */ + + static class ReverseScript implements ScriptBuilder { + public change build_script( + final boolean[] changed0,int len0, + final boolean[] changed1,int len1) + { + change script = null; + int i0 = 0, i1 = 0; + while (i0 < len0 || i1 < len1) { + if (changed0[1+i0] || changed1[1+i1]) { + int line0 = i0, line1 = i1; + + /* Find # lines changed here in each file. */ + while (changed0[1+i0]) ++i0; + while (changed1[1+i1]) ++i1; + + /* Record this change. */ + script = new change(line0, line1, i0 - line0, i1 - line1, script); + } + + /* We have reached lines in the two files that match each other. */ + i0++; i1++; + } + + return script; + } + } + + static class ForwardScript implements ScriptBuilder { + /** Scan the tables of which lines are inserted and deleted, + producing an edit script in forward order. */ + public change build_script( + final boolean[] changed0,int len0, + final boolean[] changed1,int len1) + { + change script = null; + int i0 = len0, i1 = len1; + + while (i0 >= 0 || i1 >= 0) + { + if (changed0[i0] || changed1[i1]) + { + int line0 = i0, line1 = i1; + + /* Find # lines changed here in each file. */ + while (changed0[i0]) --i0; + while (changed1[i1]) --i1; + + /* Record this change. */ + script = new change(i0, i1, line0 - i0, line1 - i1, script); + } + + /* We have reached lines in the two files that match each other. */ + i0--; i1--; + } + + return script; + } + } + + /** Standard ScriptBuilders. */ + public final static ScriptBuilder + forwardScript = new ForwardScript(), + reverseScript = new ReverseScript(); + + /* Report the differences of two files. DEPTH is the current directory + depth. */ + public final change diff_2(final boolean reverse) { + return diff(reverse ? reverseScript : forwardScript); + } + + /** Get the results of comparison as an edit script. The script + is described by a list of changes. The standard ScriptBuilder + implementations provide for forward and reverse edit scripts. + Alternate implementations could, for instance, list common elements + instead of differences. + @param bld an object to build the script from change flags + @return the head of a list of changes + */ + public change diff(final ScriptBuilder bld) { + + /* Some lines are obviously insertions or deletions + because they don't match anything. Detect them now, + and avoid even thinking about them in the main comparison algorithm. */ + + discard_confusing_lines (); + + /* Now do the main comparison algorithm, considering just the + undiscarded lines. */ + + xvec = filevec[0].undiscarded; + yvec = filevec[1].undiscarded; + + int diags = + filevec[0].nondiscarded_lines + filevec[1].nondiscarded_lines + 3; + fdiag = new int[diags]; + fdiagoff = filevec[1].nondiscarded_lines + 1; + bdiag = new int[diags]; + bdiagoff = filevec[1].nondiscarded_lines + 1; + + compareseq (0, filevec[0].nondiscarded_lines, + 0, filevec[1].nondiscarded_lines); + fdiag = null; + bdiag = null; + + /* Modify the results slightly to make them prettier + in cases where that can validly be done. */ + + shift_boundaries (); + + /* Get the results of comparison in the form of a chain + of `struct change's -- an edit script. */ + return bld.build_script( + filevec[0].changed_flag, + filevec[0].buffered_lines, + filevec[1].changed_flag, + filevec[1].buffered_lines + ); + + } + + /** The result of comparison is an "edit script": a chain of change objects. + Each change represents one place where some lines are deleted + and some are inserted. + + LINE0 and LINE1 are the first affected lines in the two files (origin 0). + DELETED is the number of lines deleted here from file 0. + INSERTED is the number of lines inserted here in file 1. + + If DELETED is 0 then LINE0 is the number of the line before + which the insertion was done; vice versa for INSERTED and LINE1. */ + + public static class change { + /** Previous or next edit command. */ + public change link; + /** # lines of file 1 changed here. */ + public final int inserted; + /** # lines of file 0 changed here. */ + public final int deleted; + /** Line number of 1st deleted line. */ + public final int line0; + /** Line number of 1st inserted line. */ + public final int line1; + + /** Cons an additional entry onto the front of an edit script OLD. + LINE0 and LINE1 are the first affected lines in the two files (origin 0). + DELETED is the number of lines deleted here from file 0. + INSERTED is the number of lines inserted here in file 1. + + If DELETED is 0 then LINE0 is the number of the line before + which the insertion was done; vice versa for INSERTED and LINE1. */ + public change(int line0, int line1, int deleted, int inserted, change old) { + this.line0 = line0; + this.line1 = line1; + this.inserted = inserted; + this.deleted = deleted; + this.link = old; + //System.err.println(line0+","+line1+","+inserted+","+deleted); + } + } + + /** Data on one input file being compared. + */ + + class file_data { + + /** Allocate changed array for the results of comparison. */ + void clear() { + /* Allocate a flag for each line of each file, saying whether that line + is an insertion or deletion. + Allocate an extra element, always zero, at each end of each vector. + */ + changed_flag = new boolean[buffered_lines + 2]; + } + + /** Return equiv_count[I] as the number of lines in this file + that fall in equivalence class I. + @return the array of equivalence class counts. + */ + int[] equivCount() { + int[] equiv_count = new int[equiv_max]; + for (int i = 0; i < buffered_lines; ++i) + ++equiv_count[equivs[i]]; + return equiv_count; + } + + /** Discard lines that have no matches in another file. + + A line which is discarded will not be considered by the actual + comparison algorithm; it will be as if that line were not in the file. + The file's `realindexes' table maps virtual line numbers + (which don't count the discarded lines) into real line numbers; + this is how the actual comparison algorithm produces results + that are comprehensible when the discarded lines are counted. +

+ When we discard a line, we also mark it as a deletion or insertion + so that it will be printed in the output. + @param f the other file + */ + void discard_confusing_lines(file_data f) { + clear(); + /* Set up table of which lines are going to be discarded. */ + final byte[] discarded = discardable(f.equivCount()); + + /* Don't really discard the provisional lines except when they occur + in a run of discardables, with nonprovisionals at the beginning + and end. */ + filterDiscards(discarded); + + /* Actually discard the lines. */ + discard(discarded); + } + + /** Mark to be discarded each line that matches no line of another file. + If a line matches many lines, mark it as provisionally discardable. + @see equivCount() + @param counts The count of each equivalence number for the other file. + @return 0=nondiscardable, 1=discardable or 2=provisionally discardable + for each line + */ + + private byte[] discardable(final int[] counts) { + final int end = buffered_lines; + final byte[] discards = new byte[end]; + final int[] equivs = this.equivs; + int many = 5; + int tem = end / 64; + + /* Multiply MANY by approximate square root of number of lines. + That is the threshold for provisionally discardable lines. */ + while ((tem = tem >> 2) > 0) + many *= 2; + + for (int i = 0; i < end; i++) + { + int nmatch; + if (equivs[i] == 0) + continue; + nmatch = counts[equivs[i]]; + if (nmatch == 0) + discards[i] = 1; + else if (nmatch > many) + discards[i] = 2; + } + return discards; + } + + /** Don't really discard the provisional lines except when they occur + in a run of discardables, with nonprovisionals at the beginning + and end. */ + + private void filterDiscards(final byte[] discards) { + final int end = buffered_lines; + + for (int i = 0; i < end; i++) + { + /* Cancel provisional discards not in middle of run of discards. */ + if (discards[i] == 2) + discards[i] = 0; + else if (discards[i] != 0) + { + /* We have found a nonprovisional discard. */ + int j; + int length; + int provisional = 0; + + /* Find end of this run of discardable lines. + Count how many are provisionally discardable. */ + for (j = i; j < end; j++) + { + if (discards[j] == 0) + break; + if (discards[j] == 2) + ++provisional; + } + + /* Cancel provisional discards at end, and shrink the run. */ + while (j > i && discards[j - 1] == 2) { + discards[--j] = 0; --provisional; + } + + /* Now we have the length of a run of discardable lines + whose first and last are not provisional. */ + length = j - i; + + /* If 1/4 of the lines in the run are provisional, + cancel discarding of all provisional lines in the run. */ + if (provisional * 4 > length) + { + while (j > i) + if (discards[--j] == 2) + discards[j] = 0; + } + else + { + int consec; + int minimum = 1; + int tem = length / 4; + + /* MINIMUM is approximate square root of LENGTH/4. + A subrun of two or more provisionals can stand + when LENGTH is at least 16. + A subrun of 4 or more can stand when LENGTH >= 64. */ + while ((tem = tem >> 2) > 0) + minimum *= 2; + minimum++; + + /* Cancel any subrun of MINIMUM or more provisionals + within the larger run. */ + for (j = 0, consec = 0; j < length; j++) + if (discards[i + j] != 2) + consec = 0; + else if (minimum == ++consec) + /* Back up to start of subrun, to cancel it all. */ + j -= consec; + else if (minimum < consec) + discards[i + j] = 0; + + /* Scan from beginning of run + until we find 3 or more nonprovisionals in a row + or until the first nonprovisional at least 8 lines in. + Until that point, cancel any provisionals. */ + for (j = 0, consec = 0; j < length; j++) + { + if (j >= 8 && discards[i + j] == 1) + break; + if (discards[i + j] == 2) { + consec = 0; discards[i + j] = 0; + } + else if (discards[i + j] == 0) + consec = 0; + else + consec++; + if (consec == 3) + break; + } + + /* I advances to the last line of the run. */ + i += length - 1; + + /* Same thing, from end. */ + for (j = 0, consec = 0; j < length; j++) + { + if (j >= 8 && discards[i - j] == 1) + break; + if (discards[i - j] == 2) { + consec = 0; discards[i - j] = 0; + } + else if (discards[i - j] == 0) + consec = 0; + else + consec++; + if (consec == 3) + break; + } + } + } + } + } + + /** Actually discard the lines. + @param discards flags lines to be discarded + */ + private void discard(final byte[] discards) { + final int end = buffered_lines; + int j = 0; + for (int i = 0; i < end; ++i) + if (no_discards || discards[i] == 0) + { + undiscarded[j] = equivs[i]; + realindexes[j++] = i; + } + else + changed_flag[1+i] = true; + nondiscarded_lines = j; + } + + file_data(Object[] data,Hashtable h) { + buffered_lines = data.length; + + equivs = new int[buffered_lines]; + undiscarded = new int[buffered_lines]; + realindexes = new int[buffered_lines]; + + for (int i = 0; i < data.length; ++i) { + Integer ir = (Integer)h.get(data[i]); + if (ir == null) + h.put(data[i],new Integer(equivs[i] = equiv_max++)); + else + equivs[i] = ir.intValue(); + } + } + + /** Adjust inserts/deletes of blank lines to join changes + as much as possible. + + We do something when a run of changed lines include a blank + line at one end and have an excluded blank line at the other. + We are free to choose which blank line is included. + `compareseq' always chooses the one at the beginning, + but usually it is cleaner to consider the following blank line + to be the "change". The only exception is if the preceding blank line + would join this change to other changes. + @param f the file being compared against + */ + + void shift_boundaries(file_data f) { + final boolean[] changed = changed_flag; + final boolean[] other_changed = f.changed_flag; + int i = 0; + int j = 0; + int i_end = buffered_lines; + int preceding = -1; + int other_preceding = -1; + + for (;;) + { + int start, end, other_start; + + /* Scan forwards to find beginning of another run of changes. + Also keep track of the corresponding point in the other file. */ + + while (i < i_end && !changed[1+i]) + { + while (other_changed[1+j++]) + /* Non-corresponding lines in the other file + will count as the preceding batch of changes. */ + other_preceding = j; + i++; + } + + if (i == i_end) + break; + + start = i; + other_start = j; + + for (;;) + { + /* Now find the end of this run of changes. */ + + while (i < i_end && changed[1+i]) i++; + end = i; + + /* If the first changed line matches the following unchanged one, + and this run does not follow right after a previous run, + and there are no lines deleted from the other file here, + then classify the first changed line as unchanged + and the following line as changed in its place. */ + + /* You might ask, how could this run follow right after another? + Only because the previous run was shifted here. */ + + if (end != i_end + && equivs[start] == equivs[end] + && !other_changed[1+j] + && end != i_end + && !((preceding >= 0 && start == preceding) + || (other_preceding >= 0 + && other_start == other_preceding))) + { + changed[1+end++] = true; + changed[1+start++] = false; + ++i; + /* Since one line-that-matches is now before this run + instead of after, we must advance in the other file + to keep in synch. */ + ++j; + } + else + break; + } + + preceding = i; + other_preceding = j; + } + } + + /** Number of elements (lines) in this file. */ + final int buffered_lines; + + /** Vector, indexed by line number, containing an equivalence code for + each line. It is this vector that is actually compared with that + of another file to generate differences. */ + private final int[] equivs; + + /** Vector, like the previous one except that + the elements for discarded lines have been squeezed out. */ + final int[] undiscarded; + + /** Vector mapping virtual line numbers (not counting discarded lines) + to real ones (counting those lines). Both are origin-0. */ + final int[] realindexes; + + /** Total number of nondiscarded lines. */ + int nondiscarded_lines; + + /** Array, indexed by real origin-1 line number, + containing true for a line that is an insertion or a deletion. + The results of comparison are stored here. */ + boolean[] changed_flag; + + } +} diff --git a/src/partest/scala/tools/partest/nest/DiffPrint.java b/src/partest/scala/tools/partest/nest/DiffPrint.java new file mode 100644 index 000000000..74f896e6b --- /dev/null +++ b/src/partest/scala/tools/partest/nest/DiffPrint.java @@ -0,0 +1,578 @@ +/* $Log$ + * Revision 1.1 2003/10/13 16:00:11 michelou + * - Java implementation of GNU diff (GPL licence) + * + * Revision 1.4 2003/04/22 01:50:47 stuart + * add Unified format diff + * + * Revision 1.3 2003/04/22 01:00:32 stuart + * added context diff format + * + * Revision 1.2 2000/03/02 16:59:54 stuart + * add GPL + * + */ + +//package bmsi.util; +package scala.tools.partest.nest; + +import java.io.*; +import java.util.Vector; +import java.util.Date; +//import bmsi.util.Diff; +//import com.objectspace.jgl.predicates.UnaryPredicate; + +interface UnaryPredicate { + boolean execute(Object obj); +} + +/** A simple framework for printing change lists produced by Diff. + @see bmsi.util.Diff + @author Stuart D. Gathman + Copyright (C) 2000 Business Management Systems, Inc. +

+ This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 1, or (at your option) + any later version. +

+ This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +

+ You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +public class DiffPrint { + /** A Base class for printing edit scripts produced by Diff. + This class divides the change list into "hunks", and calls + print_hunk for each hunk. Various utility methods + are provided as well. + */ + public static abstract class Base { + protected Base(Object[] a,Object[] b) { + outfile = new PrintWriter(new OutputStreamWriter(System.out)); + file0 = a; + file1 = b; + } + /** Set to ignore certain kinds of lines when printing + an edit script. For example, ignoring blank lines or comments. + */ + protected UnaryPredicate ignore = null; + + /** Set to the lines of the files being compared. + */ + protected Object[] file0, file1; + + /** Divide SCRIPT into pieces by calling HUNKFUN and + print each piece with PRINTFUN. + Both functions take one arg, an edit script. + + PRINTFUN takes a subscript which belongs together (with a null + link at the end) and prints it. */ + public void print_script(Diff.change script) { + Diff.change next = script; + + while (next != null) + { + Diff.change t, end; + + /* Find a set of changes that belong together. */ + t = next; + end = hunkfun(next); + + /* Disconnect them from the rest of the changes, + making them a hunk, and remember the rest for next iteration. */ + next = end.link; + end.link = null; + //if (DEBUG) + // debug_script(t); + + /* Print this hunk. */ + print_hunk(t); + + /* Reconnect the script so it will all be freed properly. */ + end.link = next; + } + outfile.flush(); + } + + /** Called with the tail of the script + and returns the last link that belongs together with the start + of the tail. */ + + protected Diff.change hunkfun(Diff.change hunk) { + return hunk; + } + + protected int first0, last0, first1, last1, deletes, inserts; + protected PrintWriter outfile; + + /** Look at a hunk of edit script and report the range of lines in each file + that it applies to. HUNK is the start of the hunk, which is a chain + of `struct change'. The first and last line numbers of file 0 are stored + in *FIRST0 and *LAST0, and likewise for file 1 in *FIRST1 and *LAST1. + Note that these are internal line numbers that count from 0. + + If no lines from file 0 are deleted, then FIRST0 is LAST0+1. + + Also set *DELETES nonzero if any lines of file 0 are deleted + and set *INSERTS nonzero if any lines of file 1 are inserted. + If only ignorable lines are inserted or deleted, both are + set to 0. */ + + protected void analyze_hunk(Diff.change hunk) { + int f0, l0 = 0, f1, l1 = 0, show_from = 0, show_to = 0; + int i; + Diff.change next; + boolean nontrivial = (ignore == null); + + show_from = show_to = 0; + + f0 = hunk.line0; + f1 = hunk.line1; + + for (next = hunk; next != null; next = next.link) + { + l0 = next.line0 + next.deleted - 1; + l1 = next.line1 + next.inserted - 1; + show_from += next.deleted; + show_to += next.inserted; + for (i = next.line0; i <= l0 && ! nontrivial; i++) + if (!ignore.execute(file0[i])) + nontrivial = true; + for (i = next.line1; i <= l1 && ! nontrivial; i++) + if (!ignore.execute(file1[i])) + nontrivial = true; + } + + first0 = f0; + last0 = l0; + first1 = f1; + last1 = l1; + + /* If all inserted or deleted lines are ignorable, + tell the caller to ignore this hunk. */ + + if (!nontrivial) + show_from = show_to = 0; + + deletes = show_from; + inserts = show_to; + } + + /** Print the script header which identifies the files compared. */ + protected void print_header(String filea, String fileb) { } + + protected abstract void print_hunk(Diff.change hunk); + + protected void print_1_line(String pre,Object linbuf) { + outfile.println(pre + linbuf.toString()); + } + + /** Print a pair of line numbers with SEPCHAR, translated for file FILE. + If the two numbers are identical, print just one number. + + Args A and B are internal line numbers. + We print the translated (real) line numbers. */ + + protected void print_number_range (char sepchar, int a, int b) { + /* Note: we can have B < A in the case of a range of no lines. + In this case, we should print the line number before the range, + which is B. */ + if (++b > ++a) + outfile.print("" + a + sepchar + b); + else + outfile.print(b); + } + + public static char change_letter(int inserts, int deletes) { + if (inserts == 0) + return 'd'; + else if (deletes == 0) + return 'a'; + else + return 'c'; + } + } + + /** Print a change list in the standard diff format. + */ + public static class NormalPrint extends Base { + + public NormalPrint(Object[] a,Object[] b) { + super(a,b); + } + + /** Print a hunk of a normal diff. + This is a contiguous portion of a complete edit script, + describing changes in consecutive lines. */ + + protected void print_hunk (Diff.change hunk) { + + /* Determine range of line numbers involved in each file. */ + analyze_hunk(hunk); + if (deletes == 0 && inserts == 0) + return; + + /* Print out the line number header for this hunk */ + print_number_range (',', first0, last0); + outfile.print(change_letter(inserts, deletes)); + print_number_range (',', first1, last1); + outfile.println(); + + /* Print the lines that the first file has. */ + if (deletes != 0) + for (int i = first0; i <= last0; i++) + print_1_line ("< ", file0[i]); + + if (inserts != 0 && deletes != 0) + outfile.println("---"); + + /* Print the lines that the second file has. */ + if (inserts != 0) + for (int i = first1; i <= last1; i++) + print_1_line ("> ", file1[i]); + } + } + + /** Prints an edit script in a format suitable for input to ed. + The edit script must be generated with the reverse option to + be useful as actual ed input. + */ + public static class EdPrint extends Base { + + public EdPrint(Object[] a,Object[] b) { + super(a,b); + } + + /** Print a hunk of an ed diff */ + protected void print_hunk(Diff.change hunk) { + + /* Determine range of line numbers involved in each file. */ + analyze_hunk (hunk); + if (deletes == 0 && inserts == 0) + return; + + /* Print out the line number header for this hunk */ + print_number_range (',', first0, last0); + outfile.println(change_letter(inserts, deletes)); + + /* Print new/changed lines from second file, if needed */ + if (inserts != 0) + { + boolean inserting = true; + for (int i = first1; i <= last1; i++) + { + /* Resume the insert, if we stopped. */ + if (! inserting) + outfile.println(i - first1 + first0 + "a"); + inserting = true; + + /* If the file's line is just a dot, it would confuse `ed'. + So output it with a double dot, and set the flag LEADING_DOT + so that we will output another ed-command later + to change the double dot into a single dot. */ + + if (".".equals(file1[i])) + { + outfile.println(".."); + outfile.println("."); + /* Now change that double dot to the desired single dot. */ + outfile.println(i - first1 + first0 + 1 + "s/^\\.\\././"); + inserting = false; + } + else + /* Line is not `.', so output it unmodified. */ + print_1_line ("", file1[i]); + } + + /* End insert mode, if we are still in it. */ + if (inserting) + outfile.println("."); + } + } + } + + /** Prints an edit script in context diff format. This and its + 'unified' variation is used for source code patches. + */ + public static class ContextPrint extends Base { + + protected int context = 3; + + public ContextPrint(Object[] a,Object[] b) { + super(a,b); + } + + protected void print_context_label (String mark, File inf, String label) { + if (label != null) + outfile.println(mark + ' ' + label); + else if (inf.lastModified() > 0) + // FIXME: use DateFormat to get precise format needed. + outfile.println( + mark + ' ' + inf.getPath() + '\t' + new Date(inf.lastModified()) + ); + else + /* Don't pretend that standard input is ancient. */ + outfile.println(mark + ' ' + inf.getPath()); + } + + public void print_header(String filea,String fileb) { + print_context_label ("***", new File(filea), filea); + print_context_label ("---", new File(fileb), fileb); + } + + /** If function_regexp defined, search for start of function. */ + private String find_function(Object[] lines, int start) { + return null; + } + + protected void print_function(Object[] file,int start) { + String function = find_function (file0, first0); + if (function != null) { + outfile.print(" "); + outfile.print( + (function.length() < 40) ? function : function.substring(0,40) + ); + } + } + + protected void print_hunk(Diff.change hunk) { + + /* Determine range of line numbers involved in each file. */ + + analyze_hunk (hunk); + + if (deletes == 0 && inserts == 0) + return; + + /* Include a context's width before and after. */ + + first0 = Math.max(first0 - context, 0); + first1 = Math.max(first1 - context, 0); + last0 = Math.min(last0 + context, file0.length - 1); + last1 = Math.min(last1 + context, file1.length - 1); + + + outfile.print("***************"); + + /* If we looked for and found a function this is part of, + include its name in the header of the diff section. */ + print_function (file0, first0); + + outfile.println(); + outfile.print("*** "); + print_number_range (',', first0, last0); + outfile.println(" ****"); + + if (deletes != 0) { + Diff.change next = hunk; + + for (int i = first0; i <= last0; i++) { + /* Skip past changes that apply (in file 0) + only to lines before line I. */ + + while (next != null && next.line0 + next.deleted <= i) + next = next.link; + + /* Compute the marking for line I. */ + + String prefix = " "; + if (next != null && next.line0 <= i) + /* The change NEXT covers this line. + If lines were inserted here in file 1, this is "changed". + Otherwise it is "deleted". */ + prefix = (next.inserted > 0) ? "!" : "-"; + + print_1_line (prefix, file0[i]); + } + } + + outfile.print("--- "); + print_number_range (',', first1, last1); + outfile.println(" ----"); + + if (inserts != 0) { + Diff.change next = hunk; + + for (int i = first1; i <= last1; i++) { + /* Skip past changes that apply (in file 1) + only to lines before line I. */ + + while (next != null && next.line1 + next.inserted <= i) + next = next.link; + + /* Compute the marking for line I. */ + + String prefix = " "; + if (next != null && next.line1 <= i) + /* The change NEXT covers this line. + If lines were deleted here in file 0, this is "changed". + Otherwise it is "inserted". */ + prefix = (next.deleted > 0) ? "!" : "+"; + + print_1_line (prefix, file1[i]); + } + } + } + } + + /** Prints an edit script in context diff format. This and its + 'unified' variation is used for source code patches. + */ + public static class UnifiedPrint extends ContextPrint { + + public UnifiedPrint(Object[] a,Object[] b) { + super(a,b); + } + + public void print_header(String filea,String fileb) { + print_context_label ("---", new File(filea), filea); + print_context_label ("+++", new File(fileb), fileb); + } + + private void print_number_range (int a, int b) { + //translate_range (file, a, b, &trans_a, &trans_b); + + /* Note: we can have B < A in the case of a range of no lines. + In this case, we should print the line number before the range, + which is B. */ + if (b < a) + outfile.print(b + ",0"); + else + super.print_number_range(',',a,b); + } + + protected void print_hunk(Diff.change hunk) { + /* Determine range of line numbers involved in each file. */ + analyze_hunk (hunk); + + if (deletes == 0 && inserts == 0) + return; + + /* Include a context's width before and after. */ + + first0 = Math.max(first0 - context, 0); + first1 = Math.max(first1 - context, 0); + last0 = Math.min(last0 + context, file0.length - 1); + last1 = Math.min(last1 + context, file1.length - 1); + + + + outfile.print("@@ -"); + print_number_range (first0, last0); + outfile.print(" +"); + print_number_range (first1, last1); + outfile.print(" @@"); + + /* If we looked for and found a function this is part of, + include its name in the header of the diff section. */ + print_function(file0,first0); + + outfile.println(); + + Diff.change next = hunk; + int i = first0; + int j = first1; + + while (i <= last0 || j <= last1) { + + /* If the line isn't a difference, output the context from file 0. */ + + if (next == null || i < next.line0) { + outfile.print(' '); + print_1_line ("", file0[i++]); + j++; + } + else { + /* For each difference, first output the deleted part. */ + + int k = next.deleted; + while (k-- > 0) { + outfile.print('-'); + print_1_line ("", file0[i++]); + } + + /* Then output the inserted part. */ + + k = next.inserted; + while (k-- > 0) { + outfile.print('+'); + print_1_line ("", file1[j++]); + } + + /* We're done with this hunk, so on to the next! */ + + next = next.link; + } + } + } + } + + + /** Read a text file into an array of String. This provides basic diff + functionality. A more advanced diff utility will use specialized + objects to represent the text lines, with options to, for example, + convert sequences of whitespace to a single space for comparison + purposes. + */ + static String[] slurp(String file) throws IOException { + BufferedReader rdr = new BufferedReader(new FileReader(file)); + Vector s = new Vector(); + for (;;) { + String line = rdr.readLine(); + if (line == null) break; + s.addElement(line); + } + String[] a = new String[s.size()]; + s.copyInto(a); + return a; + } + + public static void main(String[] argv) throws IOException { + String filea = argv[argv.length - 2]; + String fileb = argv[argv.length - 1]; + String[] a = slurp(filea); + String[] b = slurp(fileb); + Diff d = new Diff(a,b); + char style = 'n'; + for (int i = 0; i < argv.length - 2; ++i) { + String f = argv[i]; + if (f.startsWith("-")) { + for (int j = 1; j < f.length(); ++j) { + switch (f.charAt(j)) { + case 'e': // Ed style + style = 'e'; break; + case 'c': // Context diff + style = 'c'; break; + case 'u': + style = 'u'; break; + } + } + } + } + boolean reverse = style == 'e'; + Diff.change script = d.diff_2(reverse); + if (script == null) + System.err.println("No differences"); + else { + Base p; + switch (style) { + case 'e': + p = new EdPrint(a,b); break; + case 'c': + p = new ContextPrint(a,b); break; + case 'u': + p = new UnifiedPrint(a,b); break; + default: + p = new NormalPrint(a,b); + } + p.print_header(filea,fileb); + p.print_script(script); + } + } + +} diff --git a/src/partest/scala/tools/partest/nest/FileManager.scala b/src/partest/scala/tools/partest/nest/FileManager.scala index 2a205f42a..ade5dd46b 100644 --- a/src/partest/scala/tools/partest/nest/FileManager.scala +++ b/src/partest/scala/tools/partest/nest/FileManager.scala @@ -4,7 +4,8 @@ package scala.tools.partest.nest -import java.io.{File, FilenameFilter} +import java.io.{File, FilenameFilter, ByteArrayOutputStream, PrintStream, + IOException, BufferedOutputStream} import java.net.URI object FileManager { @@ -12,7 +13,7 @@ object FileManager { val PATH_SEP = File.pathSeparatorChar val CLASSPATH = System.getProperty("java.class.path", ".") val PREFIX = System.getProperty("user.dir", ".")+"/.." - val JAVACMD = System.getProperty("nest.javacmd", "java") + val JAVACMD = System.getProperty("scalatest.javacmd", "java") /* if [ -d "$PREFIX/test" ]; then @@ -112,7 +113,7 @@ elif [ -d "$PREFIX/bin" ]; then var SCALA: String = "" var SCALAC_CMD: String = "" - val SCALAC_OPTS = System.getProperty("nest.scalac_opts", "-deprecation -encoding utf8") + val SCALAC_OPTS = System.getProperty("scalatest.scalac_opts", "-deprecation -encoding utf8") var latestFile: File = _ var latestLibFile: File = _ @@ -122,7 +123,7 @@ elif [ -d "$PREFIX/bin" ]; then findLatest() val srcDir = { - val dirname = System.getProperty("nest.cwd", "") + val dirname = System.getProperty("scalatest.cwd", "") val dir = if (dirname.isEmpty) { // guess val libDir = new File(new URI(classOf[Test].getResource("/").toString)) val path = libDir.getAbsolutePath @@ -142,12 +143,53 @@ elif [ -d "$PREFIX/bin" ]; then exit(1) } + private def basename(name: String): String = { + val inx = name.lastIndexOf(".") + if (inx < 0) name else name.substring(0, inx) + } + + def getLogFile(file: File, kind: String) = { + val dir = file.getParentFile + val fileBase = basename(file.getName) + new File(dir, fileBase + "-" + kind + ".log") + } + def deleteRecursive(dir: File) { if (dir.isDirectory) { for (file <- dir.list) deleteRecursive(new File(dir, file)) } dir.delete } + + /** + * Compares two files using a Java implementation of the GNU diff + * available at http://www.bmsi.com/java/#diff. + * + * @param f1 + * @param f2 + */ + def compareFiles(f1: File, f2: File): String = { + var res = "" + try { + val diffStream = new ByteArrayOutputStream + val diffOutput = new PrintStream( + new BufferedOutputStream(diffStream), true) + System.setOut(diffOutput) + System.setErr(diffOutput) + val args = Array(f1.getCanonicalPath(), f2.getCanonicalPath()) + DiffPrint.main(args) + System.setOut(System.out) + System.setErr(System.err) + + res = diffStream.toString + if (res.startsWith("No")) + res = "" + } catch { + case e: IOException => + e.printStackTrace() + } + res + } } class FileManager { diff --git a/src/partest/scala/tools/partest/nest/NestRunner.scala b/src/partest/scala/tools/partest/nest/NestRunner.scala index 07e2a2d33..abd155678 100644 --- a/src/partest/scala/tools/partest/nest/NestRunner.scala +++ b/src/partest/scala/tools/partest/nest/NestRunner.scala @@ -22,12 +22,16 @@ object NestRunner { private var conservative = false + var showDiff = false + var showLog = false + var failed = false + private var testFiles: List[File] = List() private val con = new PrintStream(Console.out) private var out = con private val errors = - Integer.parseInt(System.getProperty("nest.errors", "0")) + Integer.parseInt(System.getProperty("scalatest.errors", "0")) def main(args: Array[String]) { NestUI.initialize(NestUI.MANY) @@ -45,13 +49,17 @@ object NestRunner { case "--shootout" => shootoutCheck = true case "--conservative" => conservative = true case "--verbose" => NestUI._verbose = true + case "--show-diff" => showDiff = true + case "--show-log" => showLog = true + case "--failed" => failed = true case "--version" => //todo: printVersion case _ => if (arg endsWith ".scala") { val file = new File(arg) - if (file.isFile) + if (file.isFile) { + NestUI.verbose("adding test file "+file) testFiles = file :: testFiles - else { + } else { NestUI.failure("File \"" + arg + "\" not found") System.exit(1) } @@ -85,8 +93,8 @@ object NestRunner { errApp.start() val exitCode = proc.waitFor() NestUI.verbose("exit code: "+exitCode) - writer.flush() - errWriter.flush() + appender.join() + errApp.join() val scalaVersion = writer.toString + errWriter.toString NestUI.outline("Scala version is : "+scalaVersion) @@ -124,7 +132,10 @@ object NestRunner { if (check) { val fileMgr = new FileManager val kindFiles = - if (!testFiles.isEmpty) testFiles + if (!testFiles.isEmpty) { + NestUI.verbose("testing "+testFiles) + testFiles + } else fileMgr.getFiles(kind, check) if (!kindFiles.isEmpty) { NestUI.outline("\n"+msg+"\n") diff --git a/src/partest/scala/tools/partest/nest/StreamAppender.scala b/src/partest/scala/tools/partest/nest/StreamAppender.scala index 4412d37ae..eddf47cbf 100644 --- a/src/partest/scala/tools/partest/nest/StreamAppender.scala +++ b/src/partest/scala/tools/partest/nest/StreamAppender.scala @@ -7,24 +7,20 @@ package scala.tools.partest.nest import java.io.{Writer, PrintWriter, Reader, BufferedReader, IOException} -class StreamAppender(from: Reader, to: Writer) { - private val thr = new Thread(new Runnable { - def run() { - try { - val writer = new PrintWriter(to) - val reader = new BufferedReader(from) - var line = reader.readLine() - while (line != null) { - writer.println(line) - line = reader.readLine() - } - writer.flush() - } catch { - case e: IOException => - e.printStackTrace() +class StreamAppender(from: Reader, to: Writer) extends Thread { + override def run() { + try { + val writer = new PrintWriter(to) + val reader = new BufferedReader(from) + var line = reader.readLine() + while (line != null) { + writer.println(line) + line = reader.readLine() } + writer.flush() + } catch { + case e: IOException => + e.printStackTrace() } - }) - - def start() { thr.start() } + } } diff --git a/src/partest/scala/tools/partest/nest/Worker.scala b/src/partest/scala/tools/partest/nest/Worker.scala index 35a633131..4b0892aef 100644 --- a/src/partest/scala/tools/partest/nest/Worker.scala +++ b/src/partest/scala/tools/partest/nest/Worker.scala @@ -5,7 +5,8 @@ package scala.tools.partest.nest import java.io.{File, FileInputStream, FileOutputStream, PrintStream, - PrintWriter, StringWriter} + PrintWriter, StringWriter, FileWriter, InputStreamReader, + FileReader} import java.net.URL @@ -36,11 +37,22 @@ class Worker extends Actor { def printInfoStart(file: File, printer: PrintWriter) { NestUI.outline("testing: ", printer) - val dir = file.getParentFile - val dirpath = dir.getAbsolutePath - val name = file.getAbsolutePath.substring(dirpath.length) - val WIDTH = 56 - NestUI.normal("[...]"+name+List.toString(List.make(WIDTH-name.length, ' ')), printer) + val filesdir = file.getAbsoluteFile.getParentFile.getParentFile + val testdir = filesdir.getParentFile + val totalWidth = 56 + val name = { + // 1. try with [...]/files/run/test.scala + val testPathLen = testdir.getAbsolutePath.length + val name = file.getAbsolutePath.substring(testPathLen) + if (name.length <= totalWidth) + name + // 2. try with [...]/run/test.scala + else { + val filesPathLen = filesdir.getAbsolutePath.length + file.getAbsolutePath.substring(filesPathLen) + } + } + NestUI.normal("[...]"+name+List.toString(List.make(totalWidth-name.length, ' ')), printer) } def printInfoEnd(success: boolean, printer: PrintWriter) { @@ -59,6 +71,8 @@ class Worker extends Actor { } } + var log = "" + def execTest(outDir: File, logFile: File) { val cmd = JAVACMD+ @@ -70,8 +84,32 @@ class Worker extends Actor { " scala.tools.nsc.MainGenericRunner"+ " Test jvm" NestUI.verbose(cmd) - val execution = Runtime.getRuntime.exec(cmd) - //TODO: use buffered I/O + + val proc = Runtime.getRuntime.exec(cmd) + val in = proc.getInputStream + val err = proc.getErrorStream + val writer = new FileWriter(logFile) + val inApp = new StreamAppender(new InputStreamReader(in), writer) + val errApp = new StreamAppender(new InputStreamReader(err), writer) + inApp.start() + errApp.start() + proc.waitFor() + inApp.join() + errApp.join() + writer.close() + + if (NestRunner.showLog) { + // produce log as string in `log` + val reader = new FileReader(logFile) + val writer = new StringWriter + val appender = new StreamAppender(reader, writer) + appender.start() + appender.join() + reader.close() + log = writer.toString + } + + /*val execution = Runtime.getRuntime.exec(cmd) val in = execution.getInputStream val out = new FileOutputStream(logFile) var c = in.read @@ -80,10 +118,10 @@ class Worker extends Actor { c = in.read } in.close - out.close + out.close*/ } - def compareOutput(dir: File, fileBase: String, kind: String, logFile: File): Boolean = { + def compareOutput(dir: File, fileBase: String, kind: String, logFile: File): String = { // if check file exists, compare with log file val checkFile = { val chkFile = new File(dir, fileBase + ".check") @@ -92,108 +130,102 @@ class Worker extends Actor { else new File(dir, fileBase + "-" + kind + ".check") } - if (!checkFile.exists || !checkFile.canRead) - true - else { - var success, equalNow = true - val bufferSize = 1024 - val originBuffer, destBuffer = new Array[Byte](bufferSize) - val originStream = new FileInputStream(logFile) - val destStream = new FileInputStream(checkFile) - - var originSize = originStream.read(originBuffer) - while (originSize >= 0) { - if (originSize == destStream.read(destBuffer)) { - for (idx <- 0 until originSize) - equalNow = equalNow && (originBuffer(idx) == destBuffer(idx)) - if (!equalNow) { - success = false - NestUI.verbose("Diff1: diffs found") - } - } else { - success = false - NestUI.verbose("Diff1: diffs found") - } - originSize = originStream.read(originBuffer) - } - if (destStream.read(destBuffer) >= 0) - success = false - success - } + if (!checkFile.exists || !checkFile.canRead) "" + else FileManager.compareFiles(logFile, checkFile) } def runJvmTests(kind: String, files: List[File]): (Int, Int) = { + NestUI.verbose("testing "+files) val compileMgr = new CompileManager var errors = 0 var success = true + var diff = "" for (file <- files) { - val swr = new StringWriter - val wr = new PrintWriter(swr) - success = true - printInfoStart(file, wr) + val fileBase: String = basename(file.getName) + NestUI.verbose(this+" running test "+fileBase) + val dir = file.getParentFile + val outDir = new File(dir, fileBase + "-" + kind + ".obj") + val logFile = new File(dir, fileBase + "-" + kind + ".log") - if (!compileMgr.shouldCompile(file, kind)) { - NestUI.verbose("compilation of "+file+" failed\n") - success = false - } else { - // -------- run test -------- - val fileBase: String = basename(file.getName) - NestUI.verbose(this+" running test "+fileBase) - val dir = file.getParentFile - val dirpath = dir.getAbsolutePath - val outDir = new File(dir, fileBase + "-" + kind + ".obj") - val logFile = new File(dir, fileBase + "-" + kind + ".log") - //TODO: detect whether we have to use Runtime.exec - val useRuntime = true + // when option "--failed" is provided + // execute test only if log file is present + // (which means it failed before) + if (!NestRunner.failed || (logFile.exists && logFile.canRead)) { + val swr = new StringWriter + val wr = new PrintWriter(swr) + success = true + diff = "" + printInfoStart(file, wr) - if (useRuntime) { - execTest(outDir, logFile) - } else { - val classpath: List[URL] = - outDir.toURL :: - List(file.getParentFile.toURL) ::: - (List.fromString(CLASSPATH, PATH_SEP) map { x => - (new File(x)).toURL }) ::: - (List.fromString(EXT_CLASSPATH, PATH_SEP) map { x => - (new File(x)).toURL }) - try { - NestUI.verbose("classpath: "+classpath) - val out = new FileOutputStream(logFile, true) - Console.withOut(new PrintStream(out)) { - ObjectRunner.run(classpath, "Test", List("jvm")) - } - out.flush - out.close - } catch { - case e: Exception => - NestUI.verbose(e+" ("+file.getPath+")") - } - } - NestUI.verbose(this+" finished running "+fileBase) - - if (!compareOutput(dir, fileBase, kind, logFile)) { - NestUI.verbose("output differs from log file\n") + if (!compileMgr.shouldCompile(file, kind)) { + NestUI.verbose("compilation of "+file+" failed\n") success = false - } + } else { + // -------- run test -------- - // delete log file and output dir - FileManager.deleteRecursive(outDir) - FileManager.deleteRecursive(logFile) - } // successful compile - if (!success) { - errors += 1 - NestUI.verbose("incremented errors: "+errors) + //TODO: detect whether we have to use Runtime.exec + val useRuntime = true + + if (useRuntime) { + execTest(outDir, logFile) + } else { + val classpath: List[URL] = + outDir.toURL :: + List(file.getParentFile.toURL) ::: + (List.fromString(CLASSPATH, PATH_SEP) map { x => + (new File(x)).toURL }) ::: + (List.fromString(EXT_CLASSPATH, PATH_SEP) map { x => + (new File(x)).toURL }) + try { + NestUI.verbose("classpath: "+classpath) + val out = new FileOutputStream(logFile, true) + Console.withOut(new PrintStream(out)) { + ObjectRunner.run(classpath, "Test", List("jvm")) + } + out.flush + out.close + } catch { + case e: Exception => + NestUI.verbose(e+" ("+file.getPath+")") + } + } + NestUI.verbose(this+" finished running "+fileBase) + + diff = compareOutput(dir, fileBase, kind, logFile) + if (!diff.equals("")) { + NestUI.verbose("output differs from log file\n") + success = false + } + + // delete output dir + FileManager.deleteRecursive(outDir) + + // delete log file only if test was successful + if (success) + FileManager.deleteRecursive(logFile) + } // successful compile + if (!success) { + errors += 1 + NestUI.verbose("incremented errors: "+errors) + } + printInfoEnd(success, wr) + wr.flush() + swr.flush() + NestUI.normal(swr.toString) + if (!success && NestRunner.showDiff) NestUI.normal(diff) + if (!success && NestRunner.showLog) NestUI.normal(log) } - printInfoEnd(success, wr) - wr.flush() - swr.flush() - NestUI.normal(swr.toString) } // for each file NestUI.verbose("finished testing "+kind+" with "+errors+" errors") NestUI.verbose("created "+compileMgr.numSeparateCompilers+" separate compilers") (files.length-errors, errors) } + /** Runs a list of tests. + * + * @param kind The test kind (pos, neg, run, etc.) + * @param files The list of test files + */ def runTests(kind: String, files: List[File]): (Int, Int) = { val compileMgr = new CompileManager var errors = 0 @@ -228,18 +260,19 @@ class Worker extends Actor { (files.length-errors, errors) } case "neg" => { - def negTestRun(file: File, wr: PrintWriter) = new CompileTestRun(file, wr) { + def negTestRun(file: File, wr: PrintWriter, log: File) = new CompileTestRun(file, wr) { def runTest() { - if (!compileMgr.shouldFailCompile(file, kind)) { + if (!compileMgr.shouldFailCompile(file, kind, log)) { succeeded = false errors += 1 - } + } //TODO: else compare log file to check file } } for (file <- files) { + val logFile = getLogFile(file, kind) val swr = new StringWriter val wr = new PrintWriter(swr) - val testRun = negTestRun(file, wr) + val testRun = negTestRun(file, wr, logFile) testRun.doAll() wr.flush() swr.flush()