Browse Source

Implement FileDiffRefiner on UASTs

Vadim Markovtsev 7 years ago
parent
commit
ba114a74e6
6 changed files with 9230 additions and 10 deletions
  1. 116 10
      diff_refiner.go
  2. 98 0
      diff_refiner_test.go
  3. 295 0
      test_data/1.java
  4. 338 0
      test_data/2.java
  5. 3938 0
      test_data/uast1.pb
  6. 4445 0
      test_data/uast2.pb

+ 116 - 10
diff_refiner.go

@@ -1,6 +1,10 @@
 package hercules
 
 import (
+	"unicode/utf8"
+
+	"github.com/sergi/go-diff/diffmatchpatch"
+	"gopkg.in/bblfsh/sdk.v1/uast"
 	"gopkg.in/src-d/go-git.v4"
 )
 
@@ -12,17 +16,17 @@ func (ref *FileDiffRefiner) Name() string {
 }
 
 func (ref *FileDiffRefiner) Provides() []string {
-	arr := [...]string{"file_diff"}
+	arr := [...]string{DependencyFileDiff}
 	return arr[:]
 }
 
 func (ref *FileDiffRefiner) Requires() []string {
-	arr := [...]string{"file_diff", "changed_uasts"}
+	arr := [...]string{DependencyFileDiff, DependencyUastChanges}
 	return arr[:]
 }
 
 func (ref *FileDiffRefiner) Features() []string {
-	arr := [...]string{"uast"}
+	arr := [...]string{FeatureUast}
 	return arr[:]
 }
 
@@ -36,20 +40,122 @@ func (ref *FileDiffRefiner) Initialize(repository *git.Repository) {
 }
 
 func (ref *FileDiffRefiner) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
-	changesList := deps["changed_uasts"].([]UASTChange)
+	changesList := deps[DependencyUastChanges].([]UASTChange)
 	changes := map[string]UASTChange{}
 	for _, change := range changesList {
 		if change.Before != nil && change.After != nil {
 			changes[change.Change.To.Name] = change
 		}
 	}
-	diffs := deps["file_diff"].(map[string]FileDiffData)
-	for fileName, _ /*diff*/ := range diffs {
-		_ /*change*/ = changes[fileName]
-		// TODO: scan diff line by line
-	}
+	diffs := deps[DependencyFileDiff].(map[string]FileDiffData)
 	result := map[string]FileDiffData{}
-	return map[string]interface{}{"file_diff": result}, nil
+	for fileName, oldDiff := range diffs {
+		suspicious := map[int][2]int{}
+		line := 0
+		for i, diff := range oldDiff.Diffs {
+			if i == len(oldDiff.Diffs)-1 {
+				break
+			}
+			if diff.Type == diffmatchpatch.DiffInsert &&
+				oldDiff.Diffs[i+1].Type == diffmatchpatch.DiffEqual {
+				matched := 0
+				runesAdded := []rune(diff.Text)
+				runesEqual := []rune(oldDiff.Diffs[i+1].Text)
+				for ; matched < len(runesAdded) && matched < len(runesEqual) &&
+					runesAdded[matched] == runesEqual[matched]; matched++ {
+				}
+				if matched > 0 {
+					suspicious[i] = [2]int{line, matched}
+				}
+			}
+			if diff.Type != diffmatchpatch.DiffDelete {
+				line += utf8.RuneCountInString(diff.Text)
+			}
+		}
+		if len(suspicious) == 0 {
+			result[fileName] = oldDiff
+			continue
+		}
+		uastChange := changes[fileName]
+		line2node := make([][]*uast.Node, oldDiff.NewLinesOfCode)
+		visitEachNode(uastChange.After, func(node *uast.Node) {
+			if node.StartPosition != nil && node.EndPosition != nil {
+				for l := node.StartPosition.Line; l <= node.EndPosition.Line; l++ {
+					nodes := line2node[l-1]  // line starts with 1
+					if nodes == nil {
+						nodes = []*uast.Node{}
+					}
+					line2node[l-1] = append(nodes, node)
+				}
+			}
+		})
+		newDiff := FileDiffData{
+			OldLinesOfCode: oldDiff.OldLinesOfCode,
+			NewLinesOfCode: oldDiff.NewLinesOfCode,
+			Diffs:          []diffmatchpatch.Diff{},
+		}
+		skipNext := false
+		for i, diff := range oldDiff.Diffs {
+			if skipNext {
+				skipNext = false
+				continue
+			}
+			info, exists := suspicious[i]
+			if !exists {
+				newDiff.Diffs = append(newDiff.Diffs, diff)
+				continue
+			}
+			line := info[0]
+			matched := info[1]
+			size := utf8.RuneCountInString(diff.Text)
+			n1 := countNodesInInterval(line2node, line, line+size)
+			n2 := countNodesInInterval(line2node, line+matched, line+size+matched)
+			if n1 <= n2 {
+				newDiff.Diffs = append(newDiff.Diffs, diff)
+				continue
+			}
+			skipNext = true
+			runes := []rune(diff.Text)
+			newDiff.Diffs = append(newDiff.Diffs, diffmatchpatch.Diff{
+				Type: diffmatchpatch.DiffEqual, Text: string(runes[:matched]),
+			})
+			newDiff.Diffs = append(newDiff.Diffs, diffmatchpatch.Diff{
+				Type: diffmatchpatch.DiffInsert, Text: string(runes[matched:]) + string(runes[:matched]),
+			})
+			runes = []rune(oldDiff.Diffs[i+1].Text)
+			if len(runes) > matched {
+				newDiff.Diffs = append(newDiff.Diffs, diffmatchpatch.Diff{
+					Type: diffmatchpatch.DiffEqual, Text: string(runes[matched:]),
+				})
+			}
+		}
+		result[fileName] = newDiff
+	}
+	return map[string]interface{}{DependencyFileDiff: result}, nil
+}
+
+// Depth first tree traversal.
+func visitEachNode(root *uast.Node, payload func(*uast.Node)) {
+	queue := []*uast.Node{}
+	queue = append(queue, root)
+	for len(queue) > 0 {
+		node := queue[len(queue)-1]
+		queue = queue[:len(queue)-1]
+		payload(node)
+		for _, child := range node.Children {
+			queue = append(queue, child)
+		}
+	}
+}
+
+func countNodesInInterval(occupiedMap [][]*uast.Node, start, end int) int {
+	nodes := map[*uast.Node]bool{}
+	for i := start; i < end; i++ {
+		for _, node := range occupiedMap[i] {
+			nodes[node] = true
+		}
+	}
+	return len(nodes)
 }
 
 func init() {

+ 98 - 0
diff_refiner_test.go

@@ -0,0 +1,98 @@
+package hercules
+
+import (
+	"io/ioutil"
+	"path"
+	"testing"
+	"unicode/utf8"
+
+	"github.com/sergi/go-diff/diffmatchpatch"
+	"github.com/gogo/protobuf/proto"
+	"github.com/stretchr/testify/assert"
+	"gopkg.in/src-d/go-git.v4/plumbing/object"
+	"gopkg.in/bblfsh/sdk.v1/uast"
+)
+
+func fixtureFileDiffRefiner() *FileDiffRefiner {
+	fd := &FileDiffRefiner{}
+	fd.Initialize(testRepository)
+	return fd
+}
+
+func TestFileDiffRefinerMeta(t *testing.T) {
+	fd := fixtureFileDiffRefiner()
+	assert.Equal(t, fd.Name(), "FileDiffRefiner")
+	assert.Equal(t, len(fd.Provides()), 1)
+	assert.Equal(t, fd.Provides()[0], DependencyFileDiff)
+	assert.Equal(t, len(fd.Requires()), 2)
+	assert.Equal(t, fd.Requires()[0], DependencyFileDiff)
+	assert.Equal(t, fd.Requires()[1], DependencyUastChanges)
+	assert.Len(t, fd.ListConfigurationOptions(), 0)
+	fd.Configure(nil)
+	features := fd.Features()
+	assert.Len(t, features, 1)
+	assert.Equal(t, features[0], FeatureUast)
+}
+
+func TestFileDiffRefinerRegistration(t *testing.T) {
+	tp, exists := Registry.registered[(&FileDiffRefiner{}).Name()]
+	assert.True(t, exists)
+	assert.Equal(t, tp.Elem().Name(), "FileDiffRefiner")
+	tps, exists := Registry.provided[(&FileDiffRefiner{}).Provides()[0]]
+	assert.True(t, exists)
+	assert.True(t, len(tps) >= 1)
+	matched := false
+	for _, tp := range tps {
+		matched = matched || tp.Elem().Name() == "FileDiffRefiner"
+	}
+	assert.True(t, matched)
+}
+
+func TestFileDiffRefinerConsume(t *testing.T) {
+	bytes1, err := ioutil.ReadFile(path.Join("test_data", "1.java"))
+	assert.Nil(t, err)
+	bytes2, err := ioutil.ReadFile(path.Join("test_data", "2.java"))
+	assert.Nil(t, err)
+	dmp := diffmatchpatch.New()
+	src, dst, _ := dmp.DiffLinesToRunes(string(bytes1), string(bytes2))
+	state := map[string]interface{}{}
+	fileDiffs := map[string]FileDiffData{}
+	const fileName = "test.java"
+	fileDiffs[fileName] = FileDiffData{
+		OldLinesOfCode: len(src),
+		NewLinesOfCode: len(dst),
+		Diffs:          dmp.DiffMainRunes(src, dst, false),
+	}
+	state[DependencyFileDiff] = fileDiffs
+	uastChanges := make([]UASTChange, 1)
+	loadUast := func(name string) *uast.Node {
+		bytes, err := ioutil.ReadFile(path.Join("test_data", name))
+		assert.Nil(t, err)
+		node := uast.Node{}
+		proto.Unmarshal(bytes, &node)
+		return &node
+	}
+	state[DependencyUastChanges] = uastChanges
+	uastChanges[0] = UASTChange{
+		Change: &object.Change{
+			From: object.ChangeEntry{Name: fileName},
+			To: object.ChangeEntry{Name: fileName}},
+		Before: loadUast("uast1.pb"), After: loadUast("uast2.pb"),
+	}
+	fd := fixtureFileDiffRefiner()
+	iresult, err := fd.Consume(state)
+	assert.Nil(t, err)
+	result := iresult[DependencyFileDiff].(map[string]FileDiffData)
+	assert.Len(t, result, 1)
+
+	oldDiff := fileDiffs[fileName]
+	newDiff := result[fileName]
+	assert.Equal(t, oldDiff.OldLinesOfCode, newDiff.OldLinesOfCode)
+	assert.Equal(t, oldDiff.NewLinesOfCode, newDiff.NewLinesOfCode)
+	assert.Equal(t, len(oldDiff.Diffs)+1, len(newDiff.Diffs))
+	assert.Equal(t, dmp.DiffText2(oldDiff.Diffs), dmp.DiffText2(newDiff.Diffs))
+	// Some hardcoded length checks
+	assert.Equal(t, utf8.RuneCountInString(newDiff.Diffs[5].Text), 11)
+	assert.Equal(t, utf8.RuneCountInString(newDiff.Diffs[6].Text), 41)
+	assert.Equal(t, utf8.RuneCountInString(newDiff.Diffs[7].Text), 231)
+}

+ 295 - 0
test_data/1.java

@@ -0,0 +1,295 @@
+/**
+ *    Copyright (C) 2012 ZeroTurnaround LLC <support@zeroturnaround.com>
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.zeroturnaround.zip.ZipEntrySource;
+import org.zeroturnaround.zip.ZipException;
+import org.zeroturnaround.zip.ZipUtil;
+
+public class ZipUtilTest extends TestCase {
+
+  public void testUnpackEntryFromFile() throws IOException {
+    final String name = "foo";
+    final byte[] contents = "bar".getBytes();
+
+    File file = File.createTempFile("temp", null);
+    try {
+      // Create the ZIP file
+      ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(file));
+      try {
+        zos.putNextEntry(new ZipEntry(name));
+        zos.write(contents);
+        zos.closeEntry();
+      }
+      finally {
+        IOUtils.closeQuietly(zos);
+      }
+
+      // Test the ZipUtil
+      byte[] actual = ZipUtil.unpackEntry(file, name);
+      assertNotNull(actual);
+      assertEquals(new String(contents), new String(actual));
+    }
+    // 1
+    
+    // 2
+    
+    // 3
+    finally {
+      FileUtils.deleteQuietly(file);
+    }
+  }
+  
+  public void testUnpackEntryFromStream() throws IOException {
+    final String name = "foo";
+    final byte[] contents = "bar".getBytes();
+
+    File file = File.createTempFile("temp", null);
+    try {
+      // Create the ZIP file
+      ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(file));
+      try {
+        zos.putNextEntry(new ZipEntry(name));
+        zos.write(contents);
+        zos.closeEntry();
+      }
+      finally {
+        IOUtils.closeQuietly(zos);
+      }
+
+      FileInputStream fis = new FileInputStream(file);
+      // Test the ZipUtil
+      byte[] actual = ZipUtil.unpackEntry(fis, name);
+      assertNotNull(actual);
+      assertEquals(new String(contents), new String(actual));
+    }
+    // 1
+    
+    // 2
+    
+    // 3
+    finally {
+      FileUtils.deleteQuietly(file);
+    }
+  }
+
+  public void testDuplicateEntryAtAdd() throws IOException {
+    File src = new File(getClass().getResource("duplicate.zip").getPath());
+
+    File dest = File.createTempFile("temp", null);
+    try {
+      ZipUtil.addEntries(src, new ZipEntrySource[0], dest);
+    }
+    finally {
+      FileUtils.deleteQuietly(dest);
+    }
+  }
+
+  public void testDuplicateEntryAtReplace() throws IOException {
+    File src = new File(getClass().getResource("duplicate.zip").getPath());
+
+    File dest = File.createTempFile("temp", null);
+    try {
+      ZipUtil.replaceEntries(src, new ZipEntrySource[0], dest);
+    }
+    finally {
+      FileUtils.deleteQuietly(dest);
+    }
+  }
+
+  public void testDuplicateEntryAtAddOrReplace() throws IOException {
+    File src = new File(getClass().getResource("duplicate.zip").getPath());
+
+    File dest = File.createTempFile("temp", null);
+    try {
+      ZipUtil.addOrReplaceEntries(src, new ZipEntrySource[0], dest);
+    }
+    finally {
+      FileUtils.deleteQuietly(dest);
+    }
+  }
+
+  public void testUnexplode() throws IOException {
+    File file = File.createTempFile("tempFile", null);
+    File tmpDir = file.getParentFile();
+
+    unexplodeWithException(file, "shouldn't be able to unexplode file that is not a directory");
+    assertTrue("Should be able to delete tmp file", file.delete());
+    unexplodeWithException(file, "shouldn't be able to unexplode file that doesn't exist");
+
+    // create empty tmp dir with the same name as deleted file
+    File dir = new File(tmpDir, file.getName());
+    dir.deleteOnExit();
+    assertTrue("Should be able to create directory with the same name as there was tmp file", dir.mkdir());
+
+    unexplodeWithException(dir, "shouldn't be able to unexplode dir that doesn't contain any files");
+
+    // unexplode should succeed with at least one file in directory
+    File.createTempFile("temp", null, dir);
+    ZipUtil.unexplode(dir);
+
+    assertTrue("zip file should exist with the same name as the directory that was unexploded", dir.exists());
+    assertTrue("unexploding input directory should have produced zip file with the same name", !dir.isDirectory());
+    assertTrue("Should be able to delete zip that was created from directory", dir.delete());
+  }
+
+  public void testPackEntry() throws Exception {
+    File fileToPack = new File(getClass().getResource("TestFile.txt").getPath());
+    File dest = File.createTempFile("temp", null);
+    ZipUtil.packEntry(fileToPack, dest);
+    assertTrue(dest.exists());
+
+    ZipUtil.explode(dest);
+    assertTrue((new File(dest, "TestFile.txt")).exists());
+    // if fails then maybe somebody changed the file contents and did not update
+    // the test
+    assertEquals(108, (new File(dest, "TestFile.txt")).length());
+  }
+
+  public void testPackEntries() throws Exception {
+    File fileToPack = new File(getClass().getResource("TestFile.txt").getPath());
+    File fileToPackII = new File(getClass().getResource("TestFile-II.txt").getPath());
+    File dest = File.createTempFile("temp", null);
+    ZipUtil.packEntries(new File[] { fileToPack, fileToPackII }, dest);
+    assertTrue(dest.exists());
+
+    ZipUtil.explode(dest);
+    assertTrue((new File(dest, "TestFile.txt")).exists());
+    assertTrue((new File(dest, "TestFile-II.txt")).exists());
+    // if fails then maybe somebody changed the file contents and did not update
+    // the test
+    assertEquals(108, (new File(dest, "TestFile.txt")).length());
+    assertEquals(103, (new File(dest, "TestFile-II.txt")).length());
+  }
+
+  public void testZipException() {
+    boolean exceptionThrown = false;
+    try {
+      ZipUtil.pack(new File("nonExistent"), new File("weeheha"));
+    }
+    catch (ZipException e) {
+      exceptionThrown = true;
+    }
+    assertTrue(exceptionThrown);
+  }
+
+  public void testPreserveRoot() throws Exception {
+    File dest = File.createTempFile("temp", null);
+    File parent = new File(getClass().getResource("TestFile.txt").getPath()).getParentFile();
+    ZipUtil.pack(parent, dest, true);
+    ZipUtil.explode(dest);
+    assertTrue((new File(dest, parent.getName())).exists());
+  }
+
+  private void unexplodeWithException(File file, String message) {
+    boolean ok = false;
+    try {
+      ZipUtil.unexplode(file);
+    }
+    catch (Exception e) {
+      ok = true;
+    }
+    assertTrue(message, ok);
+  }
+
+  public void testArchiveEquals() {
+    File src = new File(getClass().getResource("demo.zip").getPath());
+    // byte-by-byte copy
+    File src2 = new File(getClass().getResource("demo-copy.zip").getPath());
+    assertTrue(ZipUtil.archiveEquals(src, src2));
+    
+    // entry by entry copy
+    File src3 = new File(getClass().getResource("demo-copy-II.zip").getPath());
+    assertTrue(ZipUtil.archiveEquals(src, src3));
+  }
+  
+  public void testRepackArchive() throws IOException {
+    File src = new File(getClass().getResource("demo.zip").getPath());
+    File dest = File.createTempFile("temp", null);
+
+    ZipUtil.repack(src, dest, 1);
+
+    assertTrue(ZipUtil.archiveEquals(src, dest));
+  }
+
+
+  public void testContainsAnyEntry() throws IOException {
+    File src = new File(getClass().getResource("demo.zip").getPath());
+    boolean exists = ZipUtil.containsAnyEntry(src, new String[] { "foo.txt", "bar.txt" });
+    assertTrue(exists);
+
+    exists = ZipUtil.containsAnyEntry(src, new String[] { "foo.txt", "does-not-exist.txt" });
+    assertTrue(exists);
+
+    exists = ZipUtil.containsAnyEntry(src, new String[] { "does-not-exist-I.txt", "does-not-exist-II.txt" });
+    assertFalse(exists);
+  }
+
+  public void testAddEntry() throws IOException {
+    File src = new File(getClass().getResource("demo.zip").getPath());
+    final String fileName = "TestFile.txt";
+    assertFalse(ZipUtil.containsEntry(src, fileName));
+    File newEntry = new File(getClass().getResource(fileName).getPath());
+    File dest = File.createTempFile("temp.zip", null);
+
+    ZipUtil.addEntry(src, fileName, newEntry, dest);
+    assertTrue(ZipUtil.containsEntry(dest, fileName));
+  }
+
+  public void testRemoveEntry() throws IOException {
+    File src = new File(getClass().getResource("demo.zip").getPath());
+
+    File dest = File.createTempFile("temp", null);
+    try {
+      ZipUtil.removeEntry(src, "bar.txt", dest);
+      assertTrue("Result zip misses entry 'foo.txt'", ZipUtil.containsEntry(dest, "foo.txt"));
+      assertTrue("Result zip misses entry 'foo1.txt'", ZipUtil.containsEntry(dest, "foo1.txt"));
+      assertTrue("Result zip misses entry 'foo2.txt'", ZipUtil.containsEntry(dest, "foo2.txt"));
+      assertFalse("Result zip still contains 'bar.txt'", ZipUtil.containsEntry(dest, "bar.txt"));
+    }
+    finally {
+      FileUtils.deleteQuietly(dest);
+    }
+  }
+
+  public void testRemoveDirs() throws IOException {
+    File src = new File(getClass().getResource("demo-dirs.zip").getPath());
+
+    File dest = File.createTempFile("temp", null);
+    try {
+      ZipUtil.removeEntries(src, new String[] { "bar.txt", "a/b" }, dest);
+
+      assertFalse("Result zip still contains 'bar.txt'", ZipUtil.containsEntry(dest, "bar.txt"));
+      assertFalse("Result zip still contains dir 'a/b'", ZipUtil.containsEntry(dest, "a/b"));
+      assertTrue("Result doesn't containt 'attic'", ZipUtil.containsEntry(dest, "attic/treasure.txt"));
+      assertTrue("Entry whose prefix is dir name is removed too: 'b.txt'", ZipUtil.containsEntry(dest, "a/b.txt"));
+      assertFalse("Entry in a removed dir is still there: 'a/b/c.txt'", ZipUtil.containsEntry(dest, "a/b/c.txt"));
+
+    }
+    finally {
+      FileUtils.deleteQuietly(dest);
+    }
+  }
+}

+ 338 - 0
test_data/2.java

@@ -0,0 +1,338 @@
+/**
+ *    Copyright (C) 2012 ZeroTurnaround LLC <support@zeroturnaround.com>
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.zeroturnaround.zip.ZipEntrySource;
+import org.zeroturnaround.zip.ZipException;
+import org.zeroturnaround.zip.ZipUtil;
+
+public class ZipUtilTest extends TestCase {
+
+  public void testUnpackEntryFromFile() throws IOException {
+    final String name = "foo";
+    final byte[] contents = "bar".getBytes();
+
+    File file = File.createTempFile("temp", null);
+    try {
+      // Create the ZIP file
+      ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(file));
+      try {
+        zos.putNextEntry(new ZipEntry(name));
+        zos.write(contents);
+        zos.closeEntry();
+      }
+      finally {
+        IOUtils.closeQuietly(zos);
+      }
+
+      // Test the ZipUtil
+      byte[] actual = ZipUtil.unpackEntry(file, name);
+      assertNotNull(actual);
+      assertEquals(new String(contents), new String(actual));
+    }
+    // 1
+    
+    // 2
+    
+    // 3
+    finally {
+      FileUtils.deleteQuietly(file);
+    }
+  }
+  
+  public void testUnpackEntryFromStreamToFile() throws IOException {
+    final String name = "foo";
+    final byte[] contents = "bar".getBytes();
+
+    File file = File.createTempFile("temp", null);
+    try {
+      // Create the ZIP file
+      ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(file));
+      try {
+        zos.putNextEntry(new ZipEntry(name));
+        zos.write(contents);
+        zos.closeEntry();
+      }
+      finally {
+        IOUtils.closeQuietly(zos);
+      }
+
+      FileInputStream fis = new FileInputStream(file);
+
+      File outputFile = File.createTempFile("temp-output", null);
+
+      boolean result = ZipUtil.unpackEntry(fis, name, outputFile);
+      assertTrue(result);
+      
+      BufferedInputStream bis = new BufferedInputStream(new FileInputStream(outputFile));
+      byte[] actual = new byte[1024];
+      int read = bis.read(actual);
+      bis.close();
+      
+      assertEquals(new String(contents), new String(actual, 0, read));
+    }
+    // 1
+    
+    // 2
+    
+    // 3
+    finally {
+      FileUtils.deleteQuietly(file);
+    }
+  }
+  
+  public void testUnpackEntryFromStream() throws IOException {
+    final String name = "foo";
+    final byte[] contents = "bar".getBytes();
+
+    File file = File.createTempFile("temp", null);
+    try {
+      // Create the ZIP file
+      ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(file));
+      try {
+        zos.putNextEntry(new ZipEntry(name));
+        zos.write(contents);
+        zos.closeEntry();
+      }
+      finally {
+        IOUtils.closeQuietly(zos);
+      }
+
+      FileInputStream fis = new FileInputStream(file);
+      // Test the ZipUtil
+      byte[] actual = ZipUtil.unpackEntry(fis, name);
+      assertNotNull(actual);
+      assertEquals(new String(contents), new String(actual));
+    }
+    // 1
+    
+    // 2
+    
+    // 3
+    finally {
+      FileUtils.deleteQuietly(file);
+    }
+  }
+
+  public void testDuplicateEntryAtAdd() throws IOException {
+    File src = new File(getClass().getResource("duplicate.zip").getPath());
+
+    File dest = File.createTempFile("temp", null);
+    try {
+      ZipUtil.addEntries(src, new ZipEntrySource[0], dest);
+    }
+    finally {
+      FileUtils.deleteQuietly(dest);
+    }
+  }
+
+  public void testDuplicateEntryAtReplace() throws IOException {
+    File src = new File(getClass().getResource("duplicate.zip").getPath());
+
+    File dest = File.createTempFile("temp", null);
+    try {
+      ZipUtil.replaceEntries(src, new ZipEntrySource[0], dest);
+    }
+    finally {
+      FileUtils.deleteQuietly(dest);
+    }
+  }
+
+  public void testDuplicateEntryAtAddOrReplace() throws IOException {
+    File src = new File(getClass().getResource("duplicate.zip").getPath());
+
+    File dest = File.createTempFile("temp", null);
+    try {
+      ZipUtil.addOrReplaceEntries(src, new ZipEntrySource[0], dest);
+    }
+    finally {
+      FileUtils.deleteQuietly(dest);
+    }
+  }
+
+  public void testUnexplode() throws IOException {
+    File file = File.createTempFile("tempFile", null);
+    File tmpDir = file.getParentFile();
+
+    unexplodeWithException(file, "shouldn't be able to unexplode file that is not a directory");
+    assertTrue("Should be able to delete tmp file", file.delete());
+    unexplodeWithException(file, "shouldn't be able to unexplode file that doesn't exist");
+
+    // create empty tmp dir with the same name as deleted file
+    File dir = new File(tmpDir, file.getName());
+    dir.deleteOnExit();
+    assertTrue("Should be able to create directory with the same name as there was tmp file", dir.mkdir());
+
+    unexplodeWithException(dir, "shouldn't be able to unexplode dir that doesn't contain any files");
+
+    // unexplode should succeed with at least one file in directory
+    File.createTempFile("temp", null, dir);
+    ZipUtil.unexplode(dir);
+
+    assertTrue("zip file should exist with the same name as the directory that was unexploded", dir.exists());
+    assertTrue("unexploding input directory should have produced zip file with the same name", !dir.isDirectory());
+    assertTrue("Should be able to delete zip that was created from directory", dir.delete());
+  }
+
+  public void testPackEntry() throws Exception {
+    File fileToPack = new File(getClass().getResource("TestFile.txt").getPath());
+    File dest = File.createTempFile("temp", null);
+    ZipUtil.packEntry(fileToPack, dest);
+    assertTrue(dest.exists());
+
+    ZipUtil.explode(dest);
+    assertTrue((new File(dest, "TestFile.txt")).exists());
+    // if fails then maybe somebody changed the file contents and did not update
+    // the test
+    assertEquals(108, (new File(dest, "TestFile.txt")).length());
+  }
+
+  public void testPackEntries() throws Exception {
+    File fileToPack = new File(getClass().getResource("TestFile.txt").getPath());
+    File fileToPackII = new File(getClass().getResource("TestFile-II.txt").getPath());
+    File dest = File.createTempFile("temp", null);
+    ZipUtil.packEntries(new File[] { fileToPack, fileToPackII }, dest);
+    assertTrue(dest.exists());
+
+    ZipUtil.explode(dest);
+    assertTrue((new File(dest, "TestFile.txt")).exists());
+    assertTrue((new File(dest, "TestFile-II.txt")).exists());
+    // if fails then maybe somebody changed the file contents and did not update
+    // the test
+    assertEquals(108, (new File(dest, "TestFile.txt")).length());
+    assertEquals(103, (new File(dest, "TestFile-II.txt")).length());
+  }
+
+  public void testZipException() {
+    boolean exceptionThrown = false;
+    try {
+      ZipUtil.pack(new File("nonExistent"), new File("weeheha"));
+    }
+    catch (ZipException e) {
+      exceptionThrown = true;
+    }
+    assertTrue(exceptionThrown);
+  }
+
+  public void testPreserveRoot() throws Exception {
+    File dest = File.createTempFile("temp", null);
+    File parent = new File(getClass().getResource("TestFile.txt").getPath()).getParentFile();
+    ZipUtil.pack(parent, dest, true);
+    ZipUtil.explode(dest);
+    assertTrue((new File(dest, parent.getName())).exists());
+  }
+
+  private void unexplodeWithException(File file, String message) {
+    boolean ok = false;
+    try {
+      ZipUtil.unexplode(file);
+    }
+    catch (Exception e) {
+      ok = true;
+    }
+    assertTrue(message, ok);
+  }
+
+  public void testArchiveEquals() {
+    File src = new File(getClass().getResource("demo.zip").getPath());
+    // byte-by-byte copy
+    File src2 = new File(getClass().getResource("demo-copy.zip").getPath());
+    assertTrue(ZipUtil.archiveEquals(src, src2));
+    
+    // entry by entry copy
+    File src3 = new File(getClass().getResource("demo-copy-II.zip").getPath());
+    assertTrue(ZipUtil.archiveEquals(src, src3));
+  }
+  
+  public void testRepackArchive() throws IOException {
+    File src = new File(getClass().getResource("demo.zip").getPath());
+    File dest = File.createTempFile("temp", null);
+
+    ZipUtil.repack(src, dest, 1);
+
+    assertTrue(ZipUtil.archiveEquals(src, dest));
+  }
+
+
+  public void testContainsAnyEntry() throws IOException {
+    File src = new File(getClass().getResource("demo.zip").getPath());
+    boolean exists = ZipUtil.containsAnyEntry(src, new String[] { "foo.txt", "bar.txt" });
+    assertTrue(exists);
+
+    exists = ZipUtil.containsAnyEntry(src, new String[] { "foo.txt", "does-not-exist.txt" });
+    assertTrue(exists);
+
+    exists = ZipUtil.containsAnyEntry(src, new String[] { "does-not-exist-I.txt", "does-not-exist-II.txt" });
+    assertFalse(exists);
+  }
+
+  public void testAddEntry() throws IOException {
+    File src = new File(getClass().getResource("demo.zip").getPath());
+    final String fileName = "TestFile.txt";
+    assertFalse(ZipUtil.containsEntry(src, fileName));
+    File newEntry = new File(getClass().getResource(fileName).getPath());
+    File dest = File.createTempFile("temp.zip", null);
+
+    ZipUtil.addEntry(src, fileName, newEntry, dest);
+    assertTrue(ZipUtil.containsEntry(dest, fileName));
+  }
+
+  public void testRemoveEntry() throws IOException {
+    File src = new File(getClass().getResource("demo.zip").getPath());
+
+    File dest = File.createTempFile("temp", null);
+    try {
+      ZipUtil.removeEntry(src, "bar.txt", dest);
+      assertTrue("Result zip misses entry 'foo.txt'", ZipUtil.containsEntry(dest, "foo.txt"));
+      assertTrue("Result zip misses entry 'foo1.txt'", ZipUtil.containsEntry(dest, "foo1.txt"));
+      assertTrue("Result zip misses entry 'foo2.txt'", ZipUtil.containsEntry(dest, "foo2.txt"));
+      assertFalse("Result zip still contains 'bar.txt'", ZipUtil.containsEntry(dest, "bar.txt"));
+    }
+    finally {
+      FileUtils.deleteQuietly(dest);
+    }
+  }
+
+  public void testRemoveDirs() throws IOException {
+    File src = new File(getClass().getResource("demo-dirs.zip").getPath());
+
+    File dest = File.createTempFile("temp", null);
+    try {
+      ZipUtil.removeEntries(src, new String[] { "bar.txt", "a/b" }, dest);
+
+      assertFalse("Result zip still contains 'bar.txt'", ZipUtil.containsEntry(dest, "bar.txt"));
+      assertFalse("Result zip still contains dir 'a/b'", ZipUtil.containsEntry(dest, "a/b"));
+      assertTrue("Result doesn't containt 'attic'", ZipUtil.containsEntry(dest, "attic/treasure.txt"));
+      assertTrue("Entry whose prefix is dir name is removed too: 'b.txt'", ZipUtil.containsEntry(dest, "a/b.txt"));
+      assertFalse("Entry in a removed dir is still there: 'a/b/c.txt'", ZipUtil.containsEntry(dest, "a/b/c.txt"));
+
+    }
+    finally {
+      FileUtils.deleteQuietly(dest);
+    }
+  }
+}

File diff suppressed because it is too large
+ 3938 - 0
test_data/uast1.pb


File diff suppressed because it is too large
+ 4445 - 0
test_data/uast2.pb