Index: test/tools/llvm-cov/multithreaded-report.test =================================================================== --- test/tools/llvm-cov/multithreaded-report.test +++ test/tools/llvm-cov/multithreaded-report.test @@ -1,8 +1,5 @@ # Test "report" command with and without multiple threads. -# Temporarily disable the test on Windows as it doesn't support "diff -r". -REQUIRES: shell - RUN: llvm-cov report -num-threads=1 \ RUN: -path-equivalence=/tmp,%S/Inputs \ RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ Index: utils/lit/lit/TestRunner.py =================================================================== --- utils/lit/lit/TestRunner.py +++ utils/lit/lit/TestRunner.py @@ -346,14 +346,15 @@ """executeBuiltinDiff - Compare files line by line.""" args = expand_glob_expressions(cmd.args, cmd_shenv.cwd)[1:] try: - opts, args = getopt.gnu_getopt(args, "wbu", ["strip-trailing-cr"]) + opts, args = getopt.gnu_getopt(args, "wbur", ["strip-trailing-cr"]) except getopt.GetoptError as err: raise InternalShellError(cmd, "Unsupported: 'diff': %s" % str(err)) - filelines, filepaths = ([] for i in range(2)) + filelines, filepaths, dir_trees = ([] for i in range(3)) ignore_all_space = False ignore_space_change = False unified_diff = False + recursive_diff = False strip_trailing_cr = False for o, a in opts: if o == "-w": @@ -362,6 +363,8 @@ ignore_space_change = True elif o == "-u": unified_diff = True + elif o == "-r": + recursive_diff = True elif o == "--strip-trailing-cr": strip_trailing_cr = True else: @@ -370,17 +373,24 @@ if len(args) != 2: raise InternalShellError(cmd, "Error: missing or extra operand") - stderr = StringIO() - stdout = StringIO() - exitCode = 0 - try: - for file in args: - if not os.path.isabs(file): - file = os.path.realpath(os.path.join(cmd_shenv.cwd, file)) - filepaths.append(file) + def getDirTree(path, basedir=""): + # Tree is a tuple of form (dirname, child_trees). + # An empty dir has child_trees = [], a file has child_trees = None. + child_trees = [] + for dirname, child_dirs, files in os.walk(os.path.join(basedir, path)): + for child_dir in child_dirs: + child_trees.append(getDirTree(child_dir, dirname)) + for filename in files: + child_trees.append((filename, None)) + return path, sorted(child_trees) + + def compareTwoFiles(filepaths): + filelines = [] + for file in filepaths: with open(file, 'r') as f: filelines.append(f.readlines()) + exitCode = 0 def compose2(f, g): return lambda x: f(g(x)) @@ -399,6 +409,99 @@ for diff in func(filelines[0], filelines[1], filepaths[0], filepaths[1]): stdout.write(diff) exitCode = 1 + return exitCode + + def printDirVsFile(dir_path, file_path): + if os.path.getsize(file_path): + msg = "File %s is a directory while file %s is a regular file" + else: + msg = "File %s is a directory while file %s is a regular empty file" + print(msg % (dir_path, file_path)) + + def printFileVsDir(file_path, dir_path): + if os.path.getsize(file_path): + msg = "File %s is a regular file while file %s is a directory" + else: + msg = "File %s is a regular empty file while file %s is a directory" + print(msg % (file_path, dir_path)) + + def printOnlyIn(basedir, path, name): + print("Only in %s: %s" % (os.path.join(basedir, path), name)) + + def compareDirTrees(dir_trees, base_paths=["", ""]): + # Dirnames of the trees are not checked, it's caller's responsibility, + # as top-level dirnames are always different. Base paths are important + # for doing os.walk, but we don't put it into tree's dirname in order + # to speed up string comparison below and while sorting in getDirTree. + left_tree, right_tree = dir_trees[0], dir_trees[1] + left_base, right_base = base_paths[0], base_paths[1] + + # Compare two files or report file vs. directory mismatch. + if left_tree[1] is None and right_tree[1] is None: + return compareTwoFiles([os.path.join(left_base, left_tree[0]), + os.path.join(right_base, right_tree[0])]) + + if left_tree[1] is None and right_tree[1] is not None: + printFileVsDir(os.path.join(left_base, left_tree[0]), + os.path.join(right_base, right_tree[0])) + return 1 + + if left_tree[1] is not None and right_tree[1] is None: + printDirVsFile(os.path.join(left_base, left_tree[0]), + os.path.join(right_base, right_tree[0])) + return 1 + + # Compare two directories via recursive use of compareDirTrees. + exitCode = 0 + left_names = [node[0] for node in left_tree[1]] + right_names = [node[0] for node in right_tree[1]] + l, r = 0, 0 + while l < len(left_names) and r < len(right_names): + # Names are sorted in getDirTree, rely on that order. + if left_names[l] < right_names[r]: + exitCode = 1 + printOnlyIn(left_base, left_tree[0], left_names[l]) + l += 1 + elif left_names[l] > right_names[r]: + exitCode = 1 + printOnlyIn(right_base, right_tree[0], right_names[r]) + r += 1 + else: + exitCode |= compareDirTrees([left_tree[1][l], right_tree[1][r]], + [os.path.join(left_base, left_tree[0]), + os.path.join(right_base, right_tree[0])]) + l += 1 + r += 1 + + # At least one of the trees has ended. Report names from the other tree. + while l < len(left_names): + exitCode = 1 + printOnlyIn(left_base, left_tree[0], left_names[l]) + l += 1 + while r < len(right_names): + exitCode = 1 + printOnlyIn(right_base, right_tree[0], right_names[r]) + r += 1 + return exitCode + + stderr = StringIO() + stdout = StringIO() + exitCode = 0 + try: + for file in args: + if not os.path.isabs(file): + file = os.path.realpath(os.path.join(cmd_shenv.cwd, file)) + + if recursive_diff: + dir_trees.append(getDirTree(file)) + else: + filepaths.append(file) + + if not recursive_diff: + exitCode = compareTwoFiles(filepaths) + else: + exitCode = compareDirTrees(dir_trees) + except IOError as err: stderr.write("Error: 'diff' command failed, %s\n" % str(err)) exitCode = 1 Index: utils/lit/tests/Inputs/shtest-shell/diff-r.txt =================================================================== --- /dev/null +++ utils/lit/tests/Inputs/shtest-shell/diff-r.txt @@ -0,0 +1,88 @@ +# Check recursive diff ("diff -r"). + +# Create two directories for further comparison. +# RUN: rm -rf %t/dir1 %t/dir2 +# RUN: mkdir -p %t/dir1 %t/dir2 + +# Create same files in both of the dirs. +# RUN: echo "hello" > %t/dir1/f1 +# RUN: echo "hello" > %t/dir2/f1 + +# Create same subdirectories with same contents. +# RUN: mkdir -p %t/dir1/subdir %t/dir2/subdir +# RUN: echo "12345" > %t/dir1/subdir/f01 +# RUN: echo "12345" > %t/dir2/subdir/f01 +# RUN: echo -e "xxx\nzzz\nyyy" > %t/dir1/subdir/f02 +# RUN: echo -e "xxx\nzzz\nyyy" > %t/dir2/subdir/f02 + +# Create empty subdirectories with same names. +# RUN: mkdir -p %t/dir1/empty_subdir %t/dir2/empty_subdir +# RUN: diff -r %t/dir1 %t/dir2 + +# Add two empty files with different names, "diff -r" should fail. +# RUN: touch %t/dir1/dir1unique +# RUN: touch %t/dir2/dir2unique +# RUN: not diff -r %t/dir1 %t/dir2 | FileCheck --check-prefix=ERROR1 %s +# ERROR1: Only in {{.*}}/dir1: dir1unique +# ERROR1: Only in {{.*}}/dir2: dir2unique +# RUN: rm %t/dir1/dir1unique %t/dir2/dir2unique + +# Same filenames but different content, "diff -r" should fail. +# RUN: echo "00000" > %t/dir2/subdir/f01 +# RUN: not diff -r %t/dir1 %t/dir2 | FileCheck --check-prefix=ERROR2 %s +# ERROR2: diff -r {{.*}}/dir1/subdir/f01 {{.*}}/dir2/subdir/f01 +# ERROR2: < 12345 +# ERROR2: > 00000 + +# Restore the original contents. +# RUN: echo "12345" > %t/dir2/subdir/f01 + +# An extra file in one of the directories, "diff -r" should fail. +# RUN: touch %t/dir2/extrafile +# RUN: not diff -r %t/dir1 %t/dir2 | FileCheck --check-prefix=ERROR3 %s +# ERROR3: Only in {{.*}}/dir2: extrafile + +# Non-empty extra file, "diff -r" should fail. +# RUN: echo "content" > %t/dir2/extrafile +# RUN: not diff -r %t/dir1 %t/dir2 | FileCheck --check-prefix=ERROR4 %s +# ERROR4: Only in {{.*}}/dir2: extrafile +# RUN: rm %t/dir2/extrafile + +# Empty extra directory, "diff -r" should fail. +# RUN: mkdir -p %t/dir1/extra_subdir +# RUN: not diff -r %t/dir1 %t/dir2 | FileCheck --check-prefix=ERROR5 %s +# ERROR5: Only in {{.*}}/dir1: extra_subdir + +# Directory vs. empty file mismatch, diff -r should fail. +# RUN: touch %t/dir2/extra_subdir +# RUN: not diff -r %t/dir1 %t/dir2 | FileCheck --check-prefix=ERROR6 %s +# ERROR6: File {{.*}}dir1/extra_subdir is a directory while file {{.*}}dir2/extra_subdir is a regular empty file + +# Directory vs. non empty file mismatch, diff -r should fail. +# RUN: echo ZYX > %t/dir2/extra_subdir +# RUN: not diff -r %t/dir1 %t/dir2 | FileCheck --check-prefix=ERROR7 %s +# ERROR7: File {{.*}}dir1/extra_subdir is a directory while file {{.*}}dir2/extra_subdir is a regular file +# RUN: rm %t/dir2/extra_subdir + +# Non-empty extra directory, diff -r should fail. +# RUN: mkdir -p %t/dir1/extra_subdir/dir +# RUN: echo "1337" > %t/dir1/extra_subdir/dir/file +# RUN: not diff -r %t/dir1 %t/dir2 | FileCheck --check-prefix=ERROR8 %s +# ERROR8: Only in {{.*}}/dir1: extra_subdir +# RUN: rm -rf %t/dir1/extra_subdir + +# Empty file vs directory mismatch, diff -r should fail. +# RUN: touch %t/dir1/extra_file +# RUN: mkdir -p %t/dir2/extra_file +# RUN: not diff -r %t/dir1 %t/dir2 | FileCheck --check-prefix=ERROR9 %s +# ERROR9: File {{.*}}dir1/extra_file is a regular empty file while file {{.*}}dir2/extra_file is a directory + +# Non empty file vs directory mismatch, diff -r should fail. +# RUN: echo qwerty > %t/dir1/extra_file +# RUN: not diff -r %t/dir1 %t/dir2 | FileCheck --check-prefix=ERROR10 %s +# ERROR10: File {{.*}}dir1/extra_file is a regular file while file {{.*}}dir2/extra_file is a directory +# RUN: rm -rf %t/dir1/extra_file %t/dir2/extra_file + +# Sanity check for the test directory. +# RUN: diff -r %t/dir1 %t/dir2 +# RUN: rm -rf %t/dir1 %t/dir2 Index: utils/lit/tests/shtest-shell.py =================================================================== --- utils/lit/tests/shtest-shell.py +++ utils/lit/tests/shtest-shell.py @@ -71,6 +71,8 @@ # CHECK: error: command failed with exit status: 127 # CHECK: *** +# CHECK: PASS: shtest-shell :: diff-r.txt + # CHECK: FAIL: shtest-shell :: error-0.txt # CHECK: *** TEST 'shtest-shell :: error-0.txt' FAILED *** # CHECK: $ "not-a-real-command"