1#!/bin/sh
2# Ensure that shuf randomizes its input.
3
4# Copyright (C) 2006-2023 Free Software Foundation, Inc.
5
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15
16# You should have received a copy of the GNU General Public License
17# along with this program.  If not, see <https://www.gnu.org/licenses/>.
18
19. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
20print_ver_ shuf
21getlimits_
22
23seq 100 > in || framework_failure_
24
25shuf in >out || fail=1
26
27# Fail if the input is the same as the output.
28# This is a probabilistic test :-)
29# However, the odds of failure are very low: 1 in 100! (~ 1 in 10^158)
30compare in out > /dev/null && { fail=1; echo "not random?" 1>&2; }
31
32# Fail if the sorted output is not the same as the input.
33sort -n out > out1
34compare in out1 || { fail=1; echo "not a permutation" 1>&2; }
35
36# Exercise shuf's -i option.
37shuf -i 1-100 > out || fail=1
38compare in out > /dev/null && { fail=1; echo "not random?" 1>&2; }
39sort -n out > out1
40compare in out1 || { fail=1; echo "not a permutation" 1>&2; }
41
42# Exercise shuf's -r -n 0 options, with no standard input.
43shuf -r -n 0 in <&- >out || fail=1
44compare /dev/null out || fail=1
45
46# Exercise shuf's -e option.
47t=$(shuf -e a b c d e | sort | fmt)
48test "$t" = 'a b c d e' || { fail=1; echo "not a permutation" 1>&2; }
49
50# coreutils-8.22 dumps core.
51shuf -er
52test $? -eq 1 || fail=1
53
54# coreutils-8.22 and 8.23 dump core
55# with a single redundant operand with --input-range
56shuf -i0-0 1
57test $? -eq 1 || fail=1
58
59# Before coreutils-6.3, this would infloop.
60# "seq 1860" produces 8193 (8K + 1) bytes of output.
61seq 1860 | shuf > /dev/null || fail=1
62
63# coreutils-6.12 and earlier would output a newline terminator, not \0.
64shuf --zero-terminated -i 1-1 > out || fail=1
65printf '1\0' > exp || framework_failure_
66cmp out exp || { fail=1; echo "missing NUL terminator?" 1>&2; }
67
68# Ensure shuf -n operates efficiently for small n. Before coreutils-8.13
69# this would try to allocate $SIZE_MAX * sizeof(size_t)
70timeout 10 shuf -i1-$SIZE_MAX -n2 >/dev/null ||
71  { fail=1; echo "couldn't get a small subset" >&2; }
72
73# Ensure shuf -n0 doesn't read any input or open specified files
74touch unreadable || framework_failure_
75chmod 0 unreadable || framework_failure_
76if ! test -r unreadable; then
77  shuf -n0 unreadable || fail=1
78  { shuf -n1 unreadable || test $? -ne 1; } && fail=1
79fi
80
81# Multiple -n is accepted, should use the smallest value
82shuf -n10 -i0-9 -n3 -n20 > exp || framework_failure_
83c=$(wc -l < exp) || framework_failure_
84test "$c" -eq 3 || { fail=1; echo "Multiple -n failed">&2 ; }
85
86# Test error conditions
87
88# -i and -e must not be used together
89: | { shuf -i0-9 -e A B || test $? -ne 1; } &&
90  { fail=1; echo "shuf did not detect erroneous -e and -i usage.">&2 ; }
91# Test invalid value for -n
92: | { shuf -nA || test $? -ne 1; } &&
93  { fail=1; echo "shuf did not detect erroneous -n usage.">&2 ; }
94# Test multiple -i
95{ shuf -i0-9 -n10 -i8-90 || test $? -ne 1; } &&
96  { fail=1; echo "shuf did not detect multiple -i usage.">&2 ; }
97# Test invalid range
98for ARG in '1' 'A' '1-' '1-A'; do
99    { shuf -i$ARG || test $? -ne 1; } &&
100    { fail=1; echo "shuf did not detect erroneous -i$ARG usage.">&2 ; }
101done
102
103# multiple -o are forbidden
104{ shuf -i0-9 -o A -o B || test $? -ne 1; } &&
105  { fail=1; echo "shuf did not detect erroneous multiple -o usage.">&2 ; }
106# multiple random-sources are forbidden
107{ shuf -i0-9 --random-source A --random-source B || test $? -ne 1; } &&
108  { fail=1; echo "shuf did not detect multiple --random-source usage.">&2 ; }
109
110# Test --repeat option
111
112# --repeat without count should return an indefinite number of lines
113shuf --rep -i 0-10 | head -n 1000 > exp || framework_failure_
114c=$(wc -l < exp) || framework_failure_
115test "$c" -eq 1000 \
116  || { fail=1; echo "--repeat does not repeat indefinitely">&2 ; }
117
118# --repeat can output more values than the input range
119shuf --rep -i0-9 -n1000 > exp || framework_failure_
120c=$(wc -l < exp) || framework_failure_
121test "$c" -eq 1000 || { fail=1; echo "--repeat with --count failed">&2 ; }
122
123# Check output values (this is not bullet-proof, but drawing 1000 values
124# between 0 and 9 should produce all values, unless there's a bug in shuf
125# or a very poor random source, or extremely bad luck)
126c=$(sort -nu exp | paste -s -d ' ') || framework_failure_
127test "$c" = "0 1 2 3 4 5 6 7 8 9" ||
128  { fail=1; echo "--repeat produced bad output">&2 ; }
129
130# check --repeat with non-zero low value
131shuf --rep -i222-233 -n2000 > exp || framework_failure_
132c=$(sort -nu exp | paste -s -d ' ') || framework_failure_
133test "$c" = "222 223 224 225 226 227 228 229 230 231 232 233" ||
134 { fail=1; echo "--repeat produced bad output with non-zero low">&2 ; }
135
136# --repeat,-i,count=0 should not fail and produce no output
137shuf --rep -i0-9 -n0 > exp || framework_failure_
138# file size should be zero (no output from shuf)
139test \! -s exp ||
140  { fail=1; echo "--repeat,-i0-9,-n0 produced bad output">&2 ; }
141
142# --repeat with -e, without count, should repeat indefinitely
143shuf --rep -e A B C D | head -n 1000 > exp || framework_failure_
144c=$(wc -l < exp) || framework_failure_
145test "$c" -eq 1000 ||
146  { fail=1; echo "--repeat,-e does not repeat indefinitely">&2 ; }
147
148# --repeat with STDIN, without count, should repeat indefinitely
149printf "A\nB\nC\nD\nE\n" | shuf --rep | head -n 1000 > exp || framework_failure_
150c=$(wc -l < exp) || framework_failure_
151test "$c" -eq 1000 ||
152  { fail=1; echo "--repeat,STDIN does not repeat indefinitely">&2 ; }
153
154# --repeat with STDIN,count - can return move values than input lines
155printf "A\nB\nC\nD\nE\n" | shuf --rep -n2000 > exp || framework_failure_
156c=$(wc -l < exp) || framework_failure_
157test "$c" -eq 2000 ||
158  { fail=1; echo "--repeat,STDIN,count failed">&2 ; }
159
160# Check output values (this is not bullet-proof, but drawing 2000 values
161# between A and E should produce all values, unless there's a bug in shuf
162# or a very poor random source, or extremely bad luck)
163c=$(sort -u exp | paste -s -d ' ') || framework_failure_
164test "$c" = "A B C D E" ||
165  { fail=1; echo "--repeat,STDIN,count produced bad output">&2 ; }
166
167# --repeat,stdin,count=0 should not fail and produce no output
168printf "A\nB\nC\nD\nE\n" | shuf --rep -n0 > exp || framework_failure_
169# file size should be zero (no output from shuf)
170test \! -s exp ||
171  { fail=1; echo "--repeat,STDIN,-n0 produced bad output">&2 ; }
172
173# shuf 8.25 mishandles input if stdin is closed, due to glibc bug#15589.
174# See coreutils bug#25029.
175shuf /dev/null <&- >out || fail=1
176compare /dev/null out || fail=1
177
178Exit $fail
179