blob: 5e55937253c42543255a51cb3fc07a251464cbed [file] [log] [blame]
#!/usr/bin/env perl
#
# Copyright (C) 2016 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use strict;
use warnings;
use File::Temp;
use FindBin;
use Test::More;
use VCSUtils;
use lib File::Spec->catdir($FindBin::Bin, "..");
use LoadAsModule qw(PrepareChangeLog prepare-ChangeLog);
my $EXAMPLE_CPP = <<EOF;
#include <cassert>
#include <cmath>
#include <limits>
#include <iostream>
unsigned fib(unsigned);
unsigned fibPlusFive(unsigned n)
{
return fib(n) + 5;
}
unsigned fib(unsigned n)
{
// Derived from Binet's formula, <http://mathworld.wolfram.com/BinetsFibonacciNumberFormula.html>.
assert(n <= std::floor(std::log(std::numeric_limits<unsigned>::max() * std::sqrt(5)) / std::log((1 + std::sqrt(5)) / 2)));
if (!n)
return 0;
if (n == 1)
return 1;
--n;
unsigned a = 0;
unsigned b = 1;
do {
unsigned k = a + b;
a = b;
b = k;
} while (--n);
return b;
}
int main(int argc, const char* argv[])
{
std::cout << fib(1) << std::endl;
std::cout << fibPlusFive(1) << std::endl;
std::cout << fib(5) << std::endl;
std::cout << fibPlusFive(5) << std::endl;
return 0;
}
EOF
my $EXAMPLE_JAVASCRIPT_WITH_SAME_FUNCTION_DEFINED_TWICE = <<EOF;
function f() {
return 5;
}
function f() {
return 6;
}
function g() {
return 7;
}
EOF
my @testCaseHashRefs = (
###
# 0 lines of context
##
{
testName => "Unified diff with 0 context",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -15 +14,0 @@ unsigned fib(unsigned n)
- // Derived from Binet's formula, <http://mathworld.wolfram.com/BinetsFibonacciNumberFormula.html>.
EOF
expected => <<EOF
(fib):
EOF
},
{
testName => "Unified diff with 0 context; combine two adjacent functions",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -10,5 +9,0 @@ unsigned fibPlusFive(unsigned n)
- return fib(n) + 5;
-}
-
-unsigned fib(unsigned n)
-{
EOF
expected => <<EOF
(fibPlusFive):
(fib): Deleted.
EOF
},
{
testName => "Unified diff with 0 context; remove function",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -13,19 +12,0 @@ unsigned fibPlusFive(unsigned n)
-unsigned fib(unsigned n)
-{
- // Derived from Binet's formula, <http://mathworld.wolfram.com/BinetsFibonacciNumberFormula.html>.
- assert(n <= std::floor(std::log(std::numeric_limits<unsigned>::max() * std::sqrt(5)) / std::log((1 + std::sqrt(5)) / 2)));
- if (!n)
- return 0;
- if (n == 1)
- return 1;
- --n;
- unsigned a = 0;
- unsigned b = 1;
- do {
- unsigned k = a + b;
- a = b;
- b = k;
- } while (--n);
- return b;
-}
-
EOF
expected => <<EOF
(fib): Deleted.
EOF
},
{
testName => "Unified diff with 0 context; add function immediately after another function",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -11,0 +12,4 @@ unsigned fibPlusFive(unsigned n)
+unsigned fibPlusSeven(unsigned n)
+{
+ return fib(n) + 7;
+}
EOF
expected => <<EOF
(fibPlusSeven):
EOF
},
{
testName => "Unified diff with 0 context; add function at the end of the file",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -39,0 +40,5 @@ int main(int argc, const char* argv[])
+
+unsigned fibPlusSeven(unsigned n)
+{
+ return fib(n) + 7;
+}
EOF
expected => <<EOF
(fibPlusSeven):
EOF
},
{
testName => "Unified diff with 0 context; rename function",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -8 +8 @@ unsigned fib(unsigned);
-unsigned fibPlusFive(unsigned n)
+unsigned fibPlusFive2(unsigned n)
EOF
expected => <<EOF
(fibPlusFive2):
(fibPlusFive): Deleted.
EOF
},
{
testName => "Unified diff with 0 context; replace function",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -8,4 +8,5 @@ unsigned fib(unsigned);
-unsigned fibPlusFive(unsigned n)
-{
- return fib(n) + 5;
-}
+unsigned fibPlusSeven(unsigned n)
+{ // First comment
+ // Second comment
+ return fib(n) + 7;
+};
EOF
expected => <<EOF
(fibPlusSeven):
(fibPlusFive): Deleted.
EOF
},
{
testName => "Unified diff with 0 context; move function from the top of the file to the end of the file",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -8,5 +7,0 @@ unsigned fib(unsigned);
-unsigned fibPlusFive(unsigned n)
-{
- return fib(n) + 5;
-}
-
@@ -39,0 +35,5 @@ int main(int argc, const char* argv[])
+
+unsigned fibPlusFive(unsigned n)
+{
+ return fib(n) + 5;
+}
EOF
expected => <<EOF
(fibPlusFive):
EOF
},
{
testName => "Unified diff with 0 context; remove functions with the same name",
inputText => $EXAMPLE_JAVASCRIPT_WITH_SAME_FUNCTION_DEFINED_TWICE,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -1,8 +0,0 @@
-function f() {
- return 5;
-}
-
-function f() {
- return 6;
-}
-
EOF
expected => <<EOF
(f): Deleted.
EOF
},
{
# This is a contrived example and would cause a redefinition error in C++, but an analagous change in a JavaScript script would be fine.
testName => "Unified diff with 0 context; add function above function with the same name",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -33,0 +34,5 @@ int main(int argc, const char* argv[])
+ return 0;
+}
+
+int main(int argc, const char* argv[])
+{
EOF
expected => <<EOF
(main):
EOF
},
{
# This is a contrived example and would cause a redefinition error in C++, but an analagous change in a JavaScript script would be fine.
testName => "Unified diff with 0 context; add function after function with the same name",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -38,0 +39,5 @@ int main(int argc, const char* argv[])
+}
+
+int main(int argc, const char* argv[])
+{
+ return 0;
EOF
expected => <<EOF
(main):
EOF
},
###
# 3 lines of context
##
{
testName => "Unified diff with 3 lines of context",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -12,7 +12,6 @@ unsigned fibPlusFive(unsigned n)
unsigned fib(unsigned n)
{
- // Derived from Binet's formula, <http://mathworld.wolfram.com/BinetsFibonacciNumberFormula.html>.
assert(n <= std::floor(std::log(std::numeric_limits<unsigned>::max() * std::sqrt(5)) / std::log((1 + std::sqrt(5)) / 2)));
if (!n)
return 0;
EOF
expected => <<EOF
(fib):
EOF
},
{
testName => "Unified diff with 3 lines of context; combine two adjacent functions",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -7,11 +7,6 @@ unsigned fib(unsigned);
unsigned fibPlusFive(unsigned n)
{
- return fib(n) + 5;
-}
-
-unsigned fib(unsigned n)
-{
// Derived from Binet's formula, <http://mathworld.wolfram.com/BinetsFibonacciNumberFormula.html>.
assert(n <= std::floor(std::log(std::numeric_limits<unsigned>::max() * std::sqrt(5)) / std::log((1 + std::sqrt(5)) / 2)));
if (!n)
EOF
expected => <<EOF,
(fibPlusFive):
(fib): Deleted.
EOF
},
{
testName => "Unified diff with 3 lines of context; remove function",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -10,25 +10,6 @@ unsigned fibPlusFive(unsigned n)
return fib(n) + 5;
}
-unsigned fib(unsigned n)
-{
- // Derived from Binet's formula, <http://mathworld.wolfram.com/BinetsFibonacciNumberFormula.html>.
- assert(n <= std::floor(std::log(std::numeric_limits<unsigned>::max() * std::sqrt(5)) / std::log((1 + std::sqrt(5)) / 2)));
- if (!n)
- return 0;
- if (n == 1)
- return 1;
- --n;
- unsigned a = 0;
- unsigned b = 1;
- do {
- unsigned k = a + b;
- a = b;
- b = k;
- } while (--n);
- return b;
-}
-
int main(int argc, const char* argv[])
{
std::cout << fib(1) << std::endl;
EOF
# Note that this output is expected given that the chunk range includes the removed content plus context lines.
# The context lines make the after chunk range (+10, 6) overlap the implementation of fibPlusFive() and main().
expected => <<EOF,
(fibPlusFive):
(main):
(fib): Deleted.
EOF
},
###
# Diff that does not differ with respect to number of context lines
##
{
testName => "Unified diff; delete contents of entire file",
inputText => $EXAMPLE_CPP,
diffToApply => <<EOF,
diff --git a/fib.cpp b/fib.cpp
--- a/fib.cpp
+++ b/fib.cpp
@@ -1,39 +0,0 @@
-#include <cassert>
-#include <cmath>
-#include <limits>
-#include <iostream>
-
-unsigned fib(unsigned);
-
-unsigned fibPlusFive(unsigned n)
-{
- return fib(n) + 5;
-}
-
-unsigned fib(unsigned n)
-{
- // Derived from Binet's formula, <http://mathworld.wolfram.com/BinetsFibonacciNumberFormula.html>.
- assert(n <= std::floor(std::log(std::numeric_limits<unsigned>::max() * std::sqrt(5)) / std::log((1 + std::sqrt(5)) / 2)));
- if (!n)
- return 0;
- if (n == 1)
- return 1;
- --n;
- unsigned a = 0;
- unsigned b = 1;
- do {
- unsigned k = a + b;
- a = b;
- b = k;
- } while (--n);
- return b;
-}
-
-int main(int argc, const char* argv[])
-{
- std::cout << fib(1) << std::endl;
- std::cout << fibPlusFive(1) << std::endl;
- std::cout << fib(5) << std::endl;
- std::cout << fibPlusFive(5) << std::endl;
- return 0;
-}
EOF
expected => <<EOF
(fibPlusFive): Deleted.
(fib): Deleted.
(main): Deleted.
EOF
},
);
sub discardOutput(&)
{
my ($functionRef) = @_;
open(my $savedStdout, ">&STDOUT") or die "Cannot dup STDOUT: $!";
open(my $savedStderr, ">&STDERR") or die "Cannot dup STDERR: $!";
open(STDOUT, ">", File::Spec->devnull());
open(STDERR, ">", File::Spec->devnull());
&$functionRef();
open(STDOUT, ">&", $savedStdout) or die "Cannot restore stdout: $!";
open(STDERR, ">&", $savedStderr) or die "Cannot restore stderr: $!";
}
sub writeFileWithContent($$)
{
my ($file, $content) = @_;
open(FILE, ">", $file) or die "Cannot open $file: $!";
print FILE $content;
close(FILE);
}
my $testCasesCount = @testCaseHashRefs;
plan(tests => $testCasesCount);
my $temporaryDirectory = File::Temp->newdir();
my $filename = "fib.cpp";
my $originalFile = File::Spec->catfile($temporaryDirectory, $filename);
my $patchedFile = File::Spec->catfile($temporaryDirectory, "patched-$filename");
my $diffFile = File::Spec->catfile($temporaryDirectory, "a.diff");
foreach my $testCase (@testCaseHashRefs) {
writeFileWithContent($originalFile, $testCase->{inputText});
writeFileWithContent($diffFile, $testCase->{diffToApply});
my $exitCode = exitStatus(system("patch", "-s", "-d", $temporaryDirectory, "-i", $diffFile, "-o", $patchedFile));
die "Failed to apply patch for $testCase->{testName}: $exitCode" if $exitCode;
my %delegateHash = (
openDiff => sub () {
my $fileHandle;
open($fileHandle, "<", \$testCase->{diffToApply});
return $fileHandle;
},
openFile => sub () {
return unless open(PATCHED_FILE, "<", $patchedFile);
return \*PATCHED_FILE;
},
openOriginalFile => sub () {
my $fileHandle;
open($fileHandle, "<", \$testCase->{inputText});
return $fileHandle;
},
normalizePath => sub () {
return $filename;
},
);
my %functionLists;
discardOutput(sub {
PrepareChangeLog::actuallyGenerateFunctionLists([$filename], \%functionLists, undef, undef, undef, \%delegateHash);
});
chomp(my $expected = $testCase->{expected});
is($functionLists{$filename}, $expected, $testCase->{testName});
}