~sircmpwn/ctools

26a0c2912260b06779c9d6b6b832fba8e3eebbd0 — Drew DeVault 2 years ago 9e63b73
cmp: fix bugs, add conformance tests
3 files changed, 87 insertions(+), 80 deletions(-)

M src/cmp.c
A test/cmp
M test/meson.build
M src/cmp.c => src/cmp.c +31 -80
@@ 65,15 65,15 @@ main(int argc, char *argv[])

	char *file1 = argv[optind], *file2 = argv[optind + 1];

	int fd1 = -1, fd2 = -1;
	FILE *f1 = NULL, *f2 = NULL;
	if (file1[0] == '-' && file1[1] == '\0') {
		fd1 = STDIN_FILENO;
		f1 = stdin;
	}
	if (file2[0] == '-' && file2[1] == '\0') {
		fd2 = STDIN_FILENO;
		f2 = stdin;
	}

	if (fd1 == STDIN_FILENO && fd2 == STDIN_FILENO) {
	if (f1 == stdin && f2 == stdin) {
		fprintf(stderr, "cmp: behavior is unspecified when both inputs "
				"are stdin\n");
		return 2;


@@ 92,95 92,46 @@ main(int argc, char *argv[])
		}
	}

	if ((fd1 = open(file1, O_RDWR)) < 0) {
	if (!f1 && (f1 = fopen(file1, "r")) == NULL) {
		implperror(file1, mode);
		return 2;
	}
	if ((fd2 = open(file2, O_RDWR)) < 0) {
	if (!f2 && (f2 = fopen(file2, "r")) == NULL) {
		implperror(file2, mode);
		close(fd1);
		fclose(f1);
		return 2;
	}

	bool match = true;
	size_t len = 0, lineno = 0;
	ssize_t n1 = 0, n2 = 0;
	char buf1[BUFSIZ], buf2[BUFSIZ];
	while ((n1 = read(fd1, buf1, sizeof(buf1))) > 0) {
		size_t max = (size_t)n1 < sizeof(buf2) ?
			(size_t)n1 : sizeof(buf2);
		n2 = 0;
		while (max > 0 && (n2 = read(fd2, buf2, max)) > 0) {
			max -= n2;
		}

		if (n2 <= 0) {
	int ret = 0;
	size_t len, lineno = 0;
	for (len = 0; ; ++len) {
		int a = getc(f1), b = getc(f2);
		if (a == EOF && b == EOF) {
			break;
		}

		for (ssize_t i = 0; i < n1; ++i) {
			if (buf1[i] == '\n') {
				++lineno;
		} else if (a == EOF || b == EOF) {
			if (mode != SILENT) {
				fprintf(stderr, "cmp: EOF on %s\n",
						a == EOF ? file1 : file2);
			}
			if (buf1[i] == buf2[i]) {
				continue;
			ret = 1;
			break;
		} else if (a == b) {
			if (a == '\n') {
				++lineno;
			}
			switch (mode) {
			case DEFAULT:
		} else {
			if (mode != LOUD) {
				printf("%s %s differ: char %zu, line %zu\n",
						file1, file2, len + i + 1,
						lineno + 1);
				break;
			case LOUD:
				printf("%zu %o %o\n", len + i + 1,
						buf1[i], buf2[i]);
				break;
			case SILENT:
				/* This space deliberately left blank */
				break;
				       file1, file2, len + 1, lineno + 1);
			} else {
				printf("%zu %o %o\n", len + 1, a, b);
			}
			match = false;
			goto closediffer;
		}

		len += n1;
	}

	char *shorter = NULL;
	if (n1 < 0) {
		implperror(file1, mode);
		goto closefail;
	} else if (n2 < 0) {
		implperror(file2, mode);
		goto closefail;
	} else if (n1 != 0 && n2 == 0 && read(fd1, buf1, sizeof(buf1)) != 0) {
		shorter = file1;
		match = false;
	} else if (n1 == 0 && n2 != 0 && read(fd2, buf2, sizeof(buf2)) != 0) {
		shorter = file2;
		match = false;
	}

	if (shorter != NULL) {
		/* The <additional info> field shall either be null or a string
		 * that starts with a <blank> and contains no <newline>
		 * characters. Some implementations report on the number of
		 * lines in this case. */
		if (rand() % 2 == 0) {
			fprintf(stderr, "cmp: EOF on %s\n", shorter);
		} else {
			fprintf(stderr, "cmp: EOF on %s %zu lines\n",
					shorter, lineno + 1);
			ret = 1;
			break;
		}
	}

closediffer:
	close(fd1);
	close(fd2);
	return match ? 0 : 1;

closefail:
	close(fd1);
	close(fd2);
	return 2;
	fclose(f1);
	fclose(f2);
	return ret;
}

A test/cmp => test/cmp +55 -0
@@ 0,0 1,55 @@
#!/bin/sh
. "$HARNESS" cmp

should_handle_samefile() (
	[ "$(cmp "$HARNESS" "$HARNESS")" = "" ]
)

should_handle_stdin() (
	[ "$(cmp "$HARNESS" <"$HARNESS")" = "" ]
)

should_handle_difference() (
	[ "$(echo test | cmp "$HARNESS" - || true)" \
		= "$HARNESS - differ: char 1, line 1" ]
)

should_handle_mutex() (
	! cmp -ls "$HARNESS" /dev/null >/dev/null 2>&1
)

should_handle_lflag() (
	[ "$(echo test | cmp -l "$HARNESS" - || true)" \
		= "1 163 164" ]
)

should_handle_sflag() (
	[ "$(echo test | cmp -s "$HARNESS" $HARNESS)" = "" ]
)

should_handle_exitcode() (
	set +e
	trap 'set -e' EXIT
	cmp "$HARNESS" /dev/null >/dev/null 2>&1
	if ! [ $? -eq 1 ]
	then
		return 1
	fi
	cmp /dev/does/not/exist /dev/null >/dev/null 2>&1
	if ! [ $? -gt 1 ]
	then
		return 1
	fi
)

should_handle_ddash cmp "$HARNESS" "$HARNESS"

runtests \
	should_handle_ddash \
	should_handle_samefile \
	should_handle_difference \
	should_handle_stdin \
	should_handle_mutex \
	should_handle_lflag \
	should_handle_sflag \
	should_handle_exitcode

M test/meson.build => test/meson.build +1 -0
@@ 6,6 6,7 @@ test_files = [
	'chgrp',
	'chown',
	'cksum',
	'cmp',
]

foreach test_file : test_files