Module Name: src Committed By: riastradh Date: Sun Mar 2 20:00:32 UTC 2025
Modified Files: src/tests/lib/libc/stdlib: h_sort.c t_sort.sh Log Message: t_sort: Test mergesort for stability too. These test cases are trivial, but they're enough to trigger unstable heapsort and qsort. Fix some error branches while here. PR lib/58931: qsort_r() missing To generate a diff of this commit: cvs rdiff -u -r1.1 -r1.2 src/tests/lib/libc/stdlib/h_sort.c \ src/tests/lib/libc/stdlib/t_sort.sh Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/tests/lib/libc/stdlib/h_sort.c diff -u src/tests/lib/libc/stdlib/h_sort.c:1.1 src/tests/lib/libc/stdlib/h_sort.c:1.2 --- src/tests/lib/libc/stdlib/h_sort.c:1.1 Sun Mar 2 16:35:41 2025 +++ src/tests/lib/libc/stdlib/h_sort.c Sun Mar 2 20:00:32 2025 @@ -1,4 +1,4 @@ -/* $NetBSD: h_sort.c,v 1.1 2025/03/02 16:35:41 riastradh Exp $ */ +/* $NetBSD: h_sort.c,v 1.2 2025/03/02 20:00:32 riastradh Exp $ */ /*- * Copyright (c) 2025 The NetBSD Foundation, Inc. @@ -27,9 +27,11 @@ */ #include <sys/cdefs.h> -__RCSID("$NetBSD: h_sort.c,v 1.1 2025/03/02 16:35:41 riastradh Exp $"); +__RCSID("$NetBSD: h_sort.c,v 1.2 2025/03/02 20:00:32 riastradh Exp $"); +#include <assert.h> #include <err.h> +#include <getopt.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -53,25 +55,41 @@ mergesort_r_wrapper(void *a, size_t n, s err(1, "mergesort_r"); } +struct context { + const char *buf; + const size_t *linepos; +}; + static int cmp(const void *va, const void *vb, void *cookie) { - const char *buf = cookie; + const struct context *C = cookie; const size_t *a = va; const size_t *b = vb; - return strcmp(buf + *a, buf + *b); + return strcmp(C->buf + C->linepos[*a], C->buf + C->linepos[*b]); +} + +static void __dead +usage(void) +{ + + fprintf(stderr, "Usage: %s [-n] <sortfn>\n", getprogname()); + exit(1); } int main(int argc, char **argv) { + int ch; + int nflag = 0; void (*sortfn)(void *, size_t, size_t, int (*)(const void *, const void *, void *), void *); char *buf = NULL; size_t nbuf; - size_t *lines = NULL; + size_t *linepos = NULL; size_t nlines; + size_t *permutation = NULL; size_t off; ssize_t nread; char *p; @@ -82,16 +100,29 @@ main(int argc, char **argv) * Parse arguments. */ setprogname(argv[0]); - if (argc < 2) - errx(1, "Usage: %s <sortfn>", getprogname()); - if (strcmp(argv[1], "heapsort_r") == 0) + while ((ch = getopt(argc, argv, "hn")) != -1) { + switch (ch) { + case 'n': + nflag = 1; + break; + case '?': + case 'h': + default: + usage(); + } + } + argc -= optind; + argv += optind; + if (argc != 1) + usage(); + if (strcmp(argv[0], "heapsort_r") == 0) sortfn = &heapsort_r_wrapper; - else if (strcmp(argv[1], "mergesort_r") == 0) + else if (strcmp(argv[0], "mergesort_r") == 0) sortfn = &mergesort_r_wrapper; - else if (strcmp(argv[1], "qsort_r") == 0) + else if (strcmp(argv[0], "qsort_r") == 0) sortfn = &qsort_r; else - errx(1, "unknown sort: %s", argv[1]); + errx(1, "unknown sort: %s", argv[0]); /* * Allocate an initial 4K buffer. @@ -99,7 +130,7 @@ main(int argc, char **argv) nbuf = 0x1000; error = reallocarr(&buf, nbuf, 1); if (error) - err(1, "reallocarr"); + errc(1, error, "reallocarr"); /* * Read the input into a contiguous buffer. Reject input with @@ -125,7 +156,7 @@ main(int argc, char **argv) nbuf *= 2; error = reallocarr(&buf, nbuf, 1); if (error) - err(1, "reallocarr"); + errc(1, error, "reallocarr"); } } @@ -148,30 +179,47 @@ main(int argc, char **argv) } /* - * Create an array of line offsets to sort. NUL-terminate each - * line so we can use strcmp(3). + * Create an array of line positions to sort. NUL-terminate + * each line so we can use strcmp(3). */ - error = reallocarr(&lines, nlines, sizeof(lines[0])); - if (lines == NULL) - err(1, "reallocarr"); + error = reallocarr(&linepos, nlines, sizeof(linepos[0])); + if (error) + errc(1, error, "reallocarr"); i = 0; - for (p = buf; lines[i++] = p - buf, (p = strchr(p, '\n')) != NULL;) { + for (p = buf; linepos[i++] = p - buf, (p = strchr(p, '\n')) != NULL;) { *p = '\0'; if (*++p == '\0') break; } + assert(i == nlines); /* - * Sort the array of line offsets via comparison function that - * consults the buffer as a cookie. + * Create an array of permuted line numbers. */ - (*sortfn)(lines, nlines, sizeof(lines[0]), &cmp, buf); + error = reallocarr(&permutation, nlines, sizeof(permutation[0])); + if (error) + errc(1, error, "reallocarr"); + for (i = 0; i < nlines; i++) + permutation[i] = i; /* - * Print the lines in sorted order. + * Sort the lines via comparison function that consults the + * buffer as a cookie. */ - for (i = 0; i < nlines; i++) - printf("%s\n", buf + lines[i]); + (*sortfn)(permutation, nlines, sizeof(permutation[0]), &cmp, + &(struct context) { .buf = buf, .linepos = linepos }); + + /* + * Print the lines in sorted order with the original line + * numbers. + */ + for (i = 0; i < nlines; i++) { + const size_t j = permutation[i]; + if (nflag) + printf("%zu %s\n", j, buf + linepos[j]); + else + printf("%s\n", buf + linepos[j]); + } fflush(stdout); return ferror(stdout); } Index: src/tests/lib/libc/stdlib/t_sort.sh diff -u src/tests/lib/libc/stdlib/t_sort.sh:1.1 src/tests/lib/libc/stdlib/t_sort.sh:1.2 --- src/tests/lib/libc/stdlib/t_sort.sh:1.1 Sun Mar 2 16:35:41 2025 +++ src/tests/lib/libc/stdlib/t_sort.sh Sun Mar 2 20:00:32 2025 @@ -1,4 +1,4 @@ -# $NetBSD: t_sort.sh,v 1.1 2025/03/02 16:35:41 riastradh Exp $ +# $NetBSD: t_sort.sh,v 1.2 2025/03/02 20:00:32 riastradh Exp $ # # Copyright (c) 2025 The NetBSD Foundation, Inc. # All rights reserved. @@ -28,19 +28,61 @@ check_sort() { local sortfn - set -Ceu + set -eu sortfn="$1" printf 'foo\nbar\nbaz\nquux' >in1 - printf 'bar\nbaz\nfoo\nquux\n' >out - atf_check -s exit:0 -o file:out \ - "$(atf_get_srcdir)"/h_sort "$sortfn" <in1 + printf '1 bar\n2 baz\n0 foo\n3 quux\n' >out1 + atf_check -s exit:0 -o file:out1 \ + "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in1 + atf_check -s exit:0 -o empty sh -c 'exec shuffle -f - <in1 >in2' - atf_check -s exit:0 -o file:out \ + printf 'bar\nbaz\nfoo\nquux\n' >out2 + atf_check -s exit:0 -o file:out2 \ "$(atf_get_srcdir)"/h_sort "$sortfn" <in2 } +check_stablesort() +{ + local sortfn + + set -eu + + sortfn="$1" + + printf 'foo\nfoo\nfoo\nfoo\nfoo' >in1 + printf '0 foo\n1 foo\n2 foo\n3 foo\n4 foo\n' >out1 + atf_check -s exit:0 -o file:out1 \ + "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in1 + + printf 'foo\nfoo\nfoo\nfoo\nfoo\nbar\nbar\nbar\nbar\nbar' >in2 + printf '5 bar\n6 bar\n7 bar\n8 bar\n9 bar\n' >out2 + printf '0 foo\n1 foo\n2 foo\n3 foo\n4 foo\n' >>out2 + atf_check -s exit:0 -o file:out2 \ + "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in2 + + printf 'foo\nfoo\nbar\nbaz\nquux' >in3 + printf '2 bar\n3 baz\n0 foo\n1 foo\n4 quux\n' >out3 + atf_check -s exit:0 -o file:out3 \ + "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in3 + + printf 'foo\nbar\nbar\nbaz\nquux' >in4 + printf '1 bar\n2 bar\n3 baz\n0 foo\n4 quux\n' >out4 + atf_check -s exit:0 -o file:out4 \ + "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in4 + + printf 'foo\nbar\nbaz\nbaz\nquux' >in5 + printf '1 bar\n2 baz\n3 baz\n0 foo\n4 quux\n' >out5 + atf_check -s exit:0 -o file:out5 \ + "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in5 + + printf 'foo\nbar\nbaz\nquux\nquux' >in6 + printf '1 bar\n2 baz\n0 foo\n3 quux\n4 quux\n' >out6 + atf_check -s exit:0 -o file:out6 \ + "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in6 +} + sortfn_case() { local sortfn @@ -52,10 +94,22 @@ sortfn_case() atf_add_test_case "$sortfn" } +stablesortfn_case() +{ + local sortfn + + sortfn="$1" + + eval "${sortfn}_stable_head() { atf_set descr \"Test ${sortfn}\"; }" + eval "${sortfn}_stable_body() { check_stablesort $sortfn; }" + atf_add_test_case "${sortfn}_stable" +} + atf_init_test_cases() { sortfn_case heapsort_r sortfn_case mergesort_r sortfn_case qsort_r + stablesortfn_case mergesort_r }