about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rwxr-xr-xt/1000-basic.t84
-rwxr-xr-xt/1500-long.t85
-rwxr-xr-xt/2000-expr.t252
-rw-r--r--t/dirgen.pl14
-rw-r--r--t/lib.sh15
-rwxr-xr-xt/tap3112
-rwxr-xr-xt/treegen30
8 files changed, 595 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index 5ef6fcd..ce6c7b7 100644
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,9 @@ all: $(ALL)
 clean: FRC
 	rm -f lr
 
+check: FRC all
+	prove -v </dev/null
+
 install: FRC all
 	mkdir -p $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1 $(DESTDIR)$(ZSHCOMPDIR)
 	install -m0755 $(ALL) $(DESTDIR)$(BINDIR)
diff --git a/t/1000-basic.t b/t/1000-basic.t
new file mode 100755
index 0000000..016ba6f
--- /dev/null
+++ b/t/1000-basic.t
@@ -0,0 +1,84 @@
+#!/bin/sh
+. ./t/lib.sh
+
+plan 6
+
+check 'no arguments' <<'EOF'
+treegen
+lr
+<<<
+f:a
+f:b
+f:c
+>>>
+.
+a
+b
+c
+EOF
+
+check 'no arguments, recurse' <<'EOF'
+treegen
+lr
+<<<
+f:a/b/c
+>>>
+.
+a
+a/b
+a/b/c
+EOF
+
+check 'simple argument' <<'EOF'
+treegen
+lr a
+<<<
+f:a/b/c
+>>>
+a
+a/b
+a/b/c
+EOF
+
+check 'simple arguments' <<'EOF'
+treegen
+lr a d
+<<<
+f:a/b/c
+f:d/e/f
+>>>
+a
+a/b
+a/b/c
+d
+d/e
+d/e/f
+EOF
+
+check 'breadth first' <<'EOF'
+treegen
+lr -B
+<<<
+f:a/b/c
+f:a/d
+>>>
+.
+a
+a/b
+a/d
+a/b/c
+EOF
+
+check 'unsorted' <<'EOF'
+treegen
+lr -U | sort
+<<<
+f:a/b/c
+f:a/d
+>>>
+.
+a
+a/b
+a/b/c
+a/d
+EOF
diff --git a/t/1500-long.t b/t/1500-long.t
new file mode 100755
index 0000000..cf6da52
--- /dev/null
+++ b/t/1500-long.t
@@ -0,0 +1,85 @@
+#!/bin/sh
+. ./t/lib.sh
+
+plan 8
+
+check 'long output' <<'EOF'
+treegen
+lr -l
+<<<
+f:a
+f:b
+f:c
+>>> /drwx------ \d .* \d+-\d+-\d+ \d+:\d+ \.\//
+>>> /-rw-r--r-- \d .* \d+-\d+-\d+ \d+:\d+ a/
+>>> /-rw-r--r-- \d .* \d+-\d+-\d+ \d+:\d+ b/
+>>> /-rw-r--r-- \d .* \d+-\d+-\d+ \d+:\d+ c/
+EOF
+
+check 'no arguments, recurse' <<'EOF'
+treegen
+lr -l
+<<<
+f:a/b/c
+>>> /drwx------ \d .* \d+-\d+-\d+ \d+:\d+ \.\//
+>>> /drwxrwxr-x \d .* \d+-\d+-\d+ \d+:\d+ a/
+>>> /drwxrwxr-x \d .* \d+-\d+-\d+ \d+:\d+ a\/b/
+>>> /-rw-r--r-- \d .* \d+-\d+-\d+ \d+:\d+ a\/b\/c/
+EOF
+
+check 'simple argument' <<'EOF'
+treegen
+lr -l a
+<<<
+f:a/b/c
+>>> /drwxrwxr-x \d .* \d+-\d+-\d+ \d+:\d+ a/
+>>> /drwxrwxr-x \d .* \d+-\d+-\d+ \d+:\d+ a\/b/
+>>> /-rw-r--r-- \d .* \d+-\d+-\d+ \d+:\d+ a\/b\/c/
+EOF
+
+check 'simple arguments' <<'EOF'
+treegen
+lr -l a d
+<<<
+f:a/b/c
+f:d/e/f
+>>> /drwxrwxr-x \d .* \d+-\d+-\d+ \d+:\d+ a/
+>>> /drwxrwxr-x \d .* \d+-\d+-\d+ \d+:\d+ a\/b/
+>>> /-rw-r--r-- \d .* \d+-\d+-\d+ \d+:\d+ a\/b\/c/
+>>> /drwxrwxr-x \d .* \d+-\d+-\d+ \d+:\d+ d/
+>>> /drwxrwxr-x \d .* \d+-\d+-\d+ \d+:\d+ d\/e/
+>>> /-rw-r--r-- \d .* \d+-\d+-\d+ \d+:\d+ d\/e\/f/
+EOF
+
+# NB: two spaces for inode count due to -U
+check 'unsorted' <<'EOF'
+treegen
+lr -l -U
+<<<
+f:a/b/c
+f:a/d
+>>> /drwxrwxr-x  \d .* \d+-\d+-\d+ \d+:\d+ a/
+EOF
+
+check 'fifo' <<'EOF'
+mkfifo fifo
+lr -l
+>>> /prw-rw-r-- .* fifo\|/
+EOF
+
+check 'symlink' <<'EOF'
+treegen
+lr -l
+<<<
+f:a/b
+l:b:a/c
+>>> /lrw.* .* a\/c -> b$/
+EOF
+
+check 'executable' <<'EOF'
+treegen
+lr -l
+<<<
+f:a/b:0:711
+>>> /-rwx--x--x .* a\/b\*$/
+EOF
diff --git a/t/2000-expr.t b/t/2000-expr.t
new file mode 100755
index 0000000..a564baa
--- /dev/null
+++ b/t/2000-expr.t
@@ -0,0 +1,252 @@
+#!/bin/sh
+. ./t/lib.sh
+
+plan 24
+
+check 'parse error detection' <<'EOF'
+lr -t 'xyzzy'
+>>>2 /parse error: unknown expression/
+>>>= 2
+EOF
+
+check 'number too big detection' <<'EOF'
+lr -t 'size > 99999999999999999999999999999999999999999'
+>>>2 /parse error: number too big/
+>>>= 2
+EOF
+
+check 'unterminated string' <<'EOF'
+lr -t 'name == "runaway'
+>>>2 /parse error: unterminated string/
+>>>= 2
+EOF
+
+check 'invalid regex' <<'EOF'
+lr -t 'name =~ "[runaway"'
+>>>2 /parse error: invalid regex/
+>>>= 2
+EOF
+
+check 'name' <<'EOF'
+treegen
+lr -t 'name == "a"'
+<<<
+f:a
+f:b
+f:c
+>>>
+a
+EOF
+
+check 'name, no match' <<'EOF'
+treegen
+lr -t 'name == "q"'
+<<<
+f:a
+f:b
+f:c
+>>>
+EOF
+
+check 'name, inner quotes' <<'EOF'
+treegen
+lr -t 'name == "quo""te"'
+<<<
+f:quo"te
+>>>
+quo"te
+EOF
+
+check 'name, environment variable' <<'EOF'
+treegen
+export NAME=b
+lr -t 'name == $NAME'
+<<<
+f:a
+f:b
+f:c
+>>>
+b
+EOF
+
+check 'name =~' <<'EOF'
+treegen
+lr -t 'name =~ "a|b"'
+<<<
+f:a
+f:b
+f:c
+>>>
+a
+b
+EOF
+
+check 'name =~~' <<'EOF'
+treegen
+lr -t 'name =~~ "a|b"'
+<<<
+f:A
+f:B
+f:C
+>>>
+A
+B
+EOF
+
+check 'name ~~' <<'EOF'
+treegen
+lr -t 'name ~~ "*.c"'
+<<<
+f:bar.c
+f:foo.c
+f:foo.h
+>>>
+bar.c
+foo.c
+EOF
+
+check 'name ~~~' <<'EOF'
+treegen
+lr -t 'name ~~~ "*.c"'
+<<<
+f:bar.c
+f:foo.C
+f:foo.h
+>>>
+bar.c
+foo.C
+EOF
+
+check 'type = f' <<'EOF'
+treegen
+lr -t 'type = f'
+<<<
+f:a
+f:b/c
+>>>
+a
+b/c
+EOF
+
+check 'type != f' <<'EOF'
+treegen
+lr -t 'type != f'
+<<<
+f:a
+f:b/c
+>>>
+.
+b
+EOF
+
+check 'type = l' <<'EOF'
+treegen
+lr -t 'type = l'
+<<<
+f:a
+l:a:b
+>>>
+b
+EOF
+
+check 'parentheses' <<'EOF'
+treegen
+lr -t '(((type = f)))'
+<<<
+f:a
+f:b/c
+>>>
+a
+b/c
+EOF
+
+check 'logic: or' <<'EOF'
+treegen
+lr -t 'name == "a" || name == "c"'
+<<<
+f:a
+f:b
+f:c
+>>>
+a
+c
+EOF
+
+check 'logic: negation' <<'EOF'
+treegen
+lr -t 'type = f && !(name == "a" || name == "c")'
+<<<
+f:a
+f:b
+f:c
+>>>
+b
+EOF
+
+check 'logic: and' <<'EOF'
+treegen
+lr -t 'name ~~ "*a*" && name ~~ "*c*"'
+<<<
+f:abc
+f:ade
+f:ebc
+>>>
+abc
+EOF
+
+check 'size ==' <<'EOF'
+treegen
+lr -t 'size == 42'
+<<<
+f:a
+f:b:42
+f:c
+>>>
+b
+EOF
+
+check 'size =' <<'EOF'
+treegen
+lr -t 'size = 42'
+<<<
+f:a
+f:b:42
+f:c
+>>>
+b
+EOF
+
+check 'size comparison, I' <<'EOF'
+treegen
+lr -t 'size > 40 && size < 60'
+<<<
+f:a
+f:b:42
+f:c:90
+>>>
+b
+EOF
+
+check 'size comparison, II' <<'EOF'
+treegen
+lr -t 'size > 1M && size < 1G'
+<<<
+f:a
+f:b:2097152
+f:c:90
+>>>
+b
+EOF
+
+check 'size comparison, III' <<'EOF'
+treegen
+lr -t 'size >= 4k'
+<<<
+f:a:4095
+f:b:4096
+f:c:4097
+>>>
+b
+c
+EOF
+
diff --git a/t/dirgen.pl b/t/dirgen.pl
new file mode 100644
index 0000000..0431a37
--- /dev/null
+++ b/t/dirgen.pl
@@ -0,0 +1,14 @@
+#!/usr/bin/env perl -w
+use v5.16;
+
+# [fdl]
+while (<>) {
+    if (/^f:(.*?)(?::(\d+))?$/) {
+        my $size = $2 || 0;
+        print "creating file $1 of size $size";
+    } elsif (/^d:(.*)$/) {
+        print "creating dir $1";
+    } elsif (/^l:(.*?):(.*)$/) {
+        print "symlinking $1 -> $2";
+    }
+}
diff --git a/t/lib.sh b/t/lib.sh
new file mode 100644
index 0000000..30a16e1
--- /dev/null
+++ b/t/lib.sh
@@ -0,0 +1,15 @@
+export "PATH=$PWD:$PWD/t:$PATH"
+
+umask 002
+
+plan() {
+       printf '1..%d\n' "$1"
+}
+
+check() {
+	export LLVM_PROFILE_FILE=/tmp/cov/lr.$(date +%s%N)
+	TESTDIR=$(mktemp -d)
+	cd $TESTDIR
+	tap3 "$@"
+	rm -r "$TESTDIR"
+}
diff --git a/t/tap3 b/t/tap3
new file mode 100755
index 0000000..bfb9bc6
--- /dev/null
+++ b/t/tap3
@@ -0,0 +1,112 @@
+#!/usr/bin/env perl
+# tap3 [DESC] - check output/error/status of a command against a specification
+#
+# A tiny variant of shelltestrunner (format v1), just takes one test
+# case and outputs a TAP line.
+#
+# Input format:
+#
+# CMD
+# <<<
+# INPUT
+# >>>
+# OUTPUT
+# >>> /OUTPUT REGEX/
+# >>>2
+# STDERR
+# >>>2 /STDERR REGEX/
+# >>>= STATUS
+# >>>= !STATUS
+#
+# All but CMD are optional and can be put in any order,
+# Regex variants can be repeated, all patterns must match.
+# By default, STATUS is set to 0 and STDERR assumed empty.
+#
+# To the extent possible under law, the creator of this work has waived
+# all copyright and related or neighboring rights to this work.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+use strict;
+use warnings;
+use Symbol 'gensym';
+use IPC::Open3;
+
+my $cmd = "";
+my ($input, $output, @output_rx, $stderr, @stderr_rx, $status, $status_not);
+my $ignored = "";
+
+my $var = \$cmd;
+while (<STDIN>) {
+	if (/^#!? /) { next; }
+	if (/^<<<$/) { $var = \$input; $input = ""; next; }
+	if (/^>>>$/) { $var = \$output; $output = ""; next; }
+	if (/^>>>2$/) { $var = \$stderr; $stderr = ""; next; }
+	if (/^>>>\s*\/(.*)\/$/) { push @output_rx, $1; next; }
+	if (/^>>>2\s*\/(.*)\/$/) { push @stderr_rx, $1; next; }
+	if (/^>>>=\s+(\d+)$/) { $var = \$ignored; $status = $1; next; }
+	if (/^>>>=\s+!(\d+)$/) { $var = \$ignored; $status_not = $1; next; }
+	$$var .= $_;
+}
+
+chomp($cmd);
+die "No command to check given\n"  if !$cmd;
+
+my ($wtr, $rdr);
+my $err = gensym;
+my $pid = open3($wtr, $rdr, $err, "/bin/sh", "-c", $cmd);
+
+my $desc = shift || $cmd;
+$desc =~ s/\n.*//g;
+
+print $wtr $input  if (defined($input));
+close $wtr;
+my $real_output = do { local $/; <$rdr>; };
+my $real_stderr = do { local $/; <$err>; };
+waitpid($pid, 0);
+my $real_status = $? >> 8;
+
+my $r = 0;
+
+sub not_ok {
+	print "not ok - $desc\n"  if (!$r);
+	$r = 1;
+	$_[0] =~ s/^/# /mg;
+	print $_[0];
+}
+
+if (defined($output) && $real_output ne $output) {
+	not_ok("wrong output:\n$real_output");
+}
+for my $rx (@output_rx) {
+	if ($real_output !~ $rx) {
+		not_ok("output doesn't match /$rx/:\n$real_output\n");
+	}
+}
+if (defined($stderr) && $real_stderr ne $stderr) {
+	not_ok("wrong stderr:\n$real_stderr");
+}
+for my $rx (@stderr_rx) {
+	if ($real_stderr !~ $rx) {
+		not_ok("stderr doesn't match /$rx/:\n$real_stderr\n");
+	}
+}
+if (!defined($stderr) && !@stderr_rx &&
+    !defined($status) && !defined($status_not) &&
+    $real_stderr) {
+	not_ok("output to stderr:\n$real_stderr\n");
+}
+if (defined($status) && $real_status != $status) {
+	not_ok("wrong status: $real_status (expected $status)\n");
+}
+if (defined($status_not) && $real_status == $status_not) {
+	not_ok("wrong status: $real_status (expected anything else)\n");
+}
+if (!defined($status) && !defined($status_not) &&
+    !defined($stderr) && !@stderr_rx &&
+    $real_status != 0) {
+	not_ok("wrong status: $real_status (command failed)\n");
+}
+
+print "ok - $desc\n"  if (!$r);
+
+exit $r;
diff --git a/t/treegen b/t/treegen
new file mode 100755
index 0000000..1b7f0c9
--- /dev/null
+++ b/t/treegen
@@ -0,0 +1,30 @@
+#!/usr/bin/perl -w
+use v5.16;
+
+use File::Spec;
+use File::Path qw(make_path);
+
+# [fdl]
+while (<>) {
+    if (/^f:(.*?)(?::(\d+))?(?::(\d+))?$/) {
+        my $path = $1;
+        my ($_volume, $dir, $_base) = File::Spec->splitpath($path);
+        my $size = $2 || 0;
+        my $mode = $3 ? oct($3) : 0644;
+
+        make_path($dir)  if $dir;
+        open(my $out, ">", "$path") or die "can't create $path";
+        chmod($mode, $out);
+        truncate($out, $size);
+        close($out);
+    } elsif (/^d:(.*)$/) {
+        make_path($1, {mode => 0755});
+    } elsif (/^l:(.*?):(.*)$/) {
+        my $target = $1;
+        my $linkname = $2;
+        my ($_volume, $dir, $_base) = File::Spec->splitpath($linkname);
+
+        make_path($dir)  if $dir;
+        symlink $target, $linkname;
+    }
+}