1#!@BASH@
2
3# Driver for rastertogutenprint tester.
4#
5# Copyright 2007-2017 Robert Krawitz (rlk@alum.mit.edu)
6#
7# This program is free software; you can redistribute it and/or modify it
8# under the terms of the GNU General Public License as published by the Free
9# Software Foundation; either version 2 of the License, or (at your option)
10# any later version.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15# for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
20retval=0
21
22if [[ -z $srcdir || $srcdir = . ]] ; then
23    sdir=$(pwd)
24elif [[ $srcdir =~ ^/ ]] ; then
25    sdir="$srcdir"
26else
27    sdir="$(pwd)/$srcdir"
28fi
29
30export STP_DATA_PATH=${STP_DATA_PATH:-"$sdir/../xml"}
31export STP_MODULE_PATH=${STP_MODULE_PATH:-"$sdir/../main:$sdir/../main/.libs"}
32
33declare single=0
34declare verbose=0
35declare valgrind=0
36declare make_ppds=1
37declare skip_simplified=0
38declare postscript=0
39declare use_explicit_quality=0
40declare extra_genppd_opts="-Z"
41declare use_smallest_pagesize=0
42declare cupsargs=''
43declare -a printers_to_test
44declare dontrun=0
45
46if [ -n "$STP_TEST_DEBUG" ] ; then
47    echo "Would run with single=$single skip_simplified=$skip_simplified extra_genppd_opts=$extra_genppd_opts cupsargs=$cupsargs use_explicit_quality=$use_explicit_quality valopts=$valopts valgrind=$valgrind"
48    exit 0
49fi
50
51declare family=$STP_TEST_FAMILY
52declare all_models=''
53declare md5dir=''
54declare outdir=''
55declare qualarg=''
56declare npages=3
57declare jobs=${STP_PARALLEL:-1}
58declare -A all_models=
59
60if [[ -r $sdir/../../src/cups/gutenprint-users-manual.pdf ]] ; then
61    testfile="$sdir/../../src/cups/gutenprint-users-manual.pdf"
62else
63    testfile="$sdir/../../doc/gutenprint-users-manual.pdf"
64fi
65
66quality_presets=(FastEconomy Economy Draft Standard High Photo HighPhoto UltraPhoto Best)
67
68usage() {
69    cat <<'EOF'
70Usage: test-rastertogutenprint [options] [PPD files...]
71  Options:
72    -s        Run only one PPD file with a given model ID/family
73    -v        Use valgrind
74    -c        Use cachegrind
75    -g        Use GDB attach in valgrind
76    -V        Verbose output
77    -n        Don't build PPD files prior to run
78    -O dir    Save output in specified directory
79    -o opt    Set option on CUPS command line
80    -m dir    Save MD5 checksums in specified directory
81    -p pages  Specify page range of input document to use
82    -P        Use PostScript rather than PDF input
83    -t jobs   Run jobs in parallel (alternatively, use STP_PARALLEL)
84    -f family Run printers only in the particular family
85    -S        Skip simplified PPD files
86    -l        Use lowest available quality setting
87    -L        Use highest available quality setting
88    -X        Don't use explicit quality setting
89    -N	      Use the smallest available page size
90    -d	      Don't actually run the command
91EOF
92exit 0;
93}
94
95while getopts "hvcgsVnO:m:o:p:PSt:lLXf:Nd" opt ; do
96    case "$opt" in
97	h*) usage ;;
98	v) valgrind=$((valgrind + 1)) ;;
99	c) valgrind=4 ;;
100	g) valopts='--vgdb=yes --error-exitcode=1' ;;
101	s) single=1 ;;
102	V) verbose=$((verbose+1)) ;;
103	n) make_ppds=0 ;;
104	O) outdir="$OPTARG"; mkdir -p "$outdir" ;;
105	o) cupsargs="$cupsargs $OPTARG" ;;
106	m) md5dir="$OPTARG"; mkdir -p "$md5dir" ;;
107	p) npages="$OPTARG" ;;
108	P) postscript=1 ;;
109	t) jobs="$OPTARG" ;;
110	f) family="$OPTARG" ;;
111	S) skip_simplified=1 ;;
112	X) use_explicit_quality=0 ;;
113	l) use_explicit_quality=1 ;;
114	L) use_explicit_quality=2 ;;
115	N) use_smallest_pagesize=1 ;;
116	d) dontrun=1 ;;
117	\?) usage ;;
118	*) echo "Unknown argument $opt"; usage ;;
119    esac
120done
121
122STP_TEST_ROTOR=${STP_TEST_ROTOR:-0}
123STP_TEST_ROTOR_CIRCUMFERENCE=${STP_TEST_ROTOR_CIRCUMFERENCE:-1}
124
125case "$valgrind" in
126    4)
127	valopts='--tool=callgrind --dump-instr=yes --trace-jump=yes --error-exitcode=1'
128	;;
129    '')
130	;;
131    *)
132	valopts='--tool=memcheck --error-exitcode=1'
133	;;
134esac
135
136shift $((OPTIND - 1))
137
138if (( $# > 0 )) ; then
139    single=0
140    for i in $(seq 1 $#) ; do
141	(( (i - 1) % $STP_TEST_ROTOR_CIRCUMFERENCE == $STP_TEST_ROTOR )) && printers_to_test+=($1)
142	shift
143    done
144    (( ${#printers_to_test[@]} == 0 )) && exit 77
145fi
146
147(( single > 0 )) && extra_genppd_opts="$extra_genppd_opts -S"
148
149if (( STP_TEST_ROTOR_CIRCUMFERENCE > 1 &&
150	  STP_TEST_ROTOR >= 0 &&
151	  STP_TEST_ROTOR < STP_TEST_ROTOR_CIRCUMFERENCE )) ; then
152    extra_genppd_opts="$extra_genppd_opts -R $STP_TEST_ROTOR_CIRCUMFERENCE -r $STP_TEST_ROTOR"
153    STP_TEST_ROTOR=0
154    STP_TEST_ROTOR_CIRCUMFERENCE=1
155fi
156
157version="@GUTENPRINT_RELEASE_VERSION@";
158rgp="./rastertogutenprint.$version"
159cupsdir="$(cups-config --serverbin)/filter"
160cgpdftoraster="$cupsdir/cgpdftoraster"
161gstoraster="$cupsdir/gstoraster"
162imagetoraster="$cupsdir/imagetoraster"
163pdftops="$cupsdir/pdftops"
164pstops="$cupsdir/pstops"
165pstoraster="$cupsdir/pstoraster"
166
167if [[ ! -x $cgpdftoraster && ! -x $pdftops && ! -x $gstoraster ]] ; then
168    echo 'CUPS does not appear to be installed, skipping test'
169    exit 0
170fi
171
172if [[ -x $pstoraster || -x $gstoraster || -x $cgpdftoraster ]] ; then
173    pages="24-$((24 + npages - 1))"
174    (( postscript > 0 )) && pages="page-ranges=$pages"
175else
176    pages=''
177fi
178
179cleanup() {
180    [[ -n $tfile ]] && rm -f "$tfile"
181    exit 1
182}
183
184pdfjam=$(type -p pdfjam)
185[[ -z $pdfjam ]] && postscript=1
186
187if (( postscript > 0 )) ; then
188    pdftops=$(type -p pdftops)
189
190    if [[ -n $pdftops && ! -x $cgpdftoraster ]] ; then
191	tfile=$(mktemp)
192	trap cleanup 1 2 3 6 14 15 30
193	"$pdftops" -f 24 -l $((24 + npages - 1)) "$testfile" "$tfile"
194    fi
195else
196    tfile=$(mktemp)
197    trap cleanup 1 2 3 6 14 15 30
198    "$pdfjam" -q "$testfile" "$pages" -o "$tfile"
199fi
200
201case "$verbose" in
202    1)
203	export STP_SUPPRESS_VERBOSE_MESSAGES=1
204	;;
205    0|'')
206	export STP_SUPPRESS_MESSAGES=1
207	export STP_SUPPRESS_VERBOSE_MESSAGES=1
208	;;
209    *)
210	;;
211esac
212
213# Note that using CUPS arguments may trigger valgrind memory leaks in
214# CUPS.
215#cupsargs='PageSize=Custom.400.00x500.00'
216#cupsargs='PageSize=Custom.324x495 Resolution=180dpi'
217#cupsargs='PageSize=w324h495 Resolution=180dpi'
218#cupsargs='PageSize=A8'
219
220get_ppds() {
221    if [[ -n $* ]] ; then
222	for f in "$@" ; do
223	    if [[ -r $f ]] ; then
224		echo "$f"
225	    elif [[ -r ppd/C/$f ]] ; then
226		echo "ppd/C/$f"
227	    elif [[ -f ppd/C/${f}.ppd ]] ; then
228		echo "ppd/C/${f}.ppd"
229	    elif [[ -f ppd/C/${f}.ppd.gz ]] ; then
230		echo "ppd/C/${f}.ppd.gz"
231	    elif [[ -f ppd/C/${f}.ppd.GZ ]] ; then
232		echo "ppd/C/${f}.ppd.GZ"
233	    elif [[ -f ppd/C/${f}.ppd.bz2 ]] ; then
234		echo "ppd/C/${f}.ppd.bz2"
235	    elif [[ -f ppd/C/${f}.ppd.BZ2 ]] ; then
236		echo "ppd/C/${f}.ppd.BZ2"
237	    elif [[ -f ppd/C/${f}.ppd.z ]] ; then
238		echo "ppd/C/${f}.ppd.z"
239	    elif [[ -f ppd/C/${f}.ppd.Z ]] ; then
240		echo "ppd/C/${f}.ppd.Z"
241	    elif [[ -f ppd/C/stp-${f}.ppd ]] ; then
242		echo "ppd/C/stp-${f}.ppd"
243	    elif [[ -f ppd/C/stp-${f}.ppd.gz ]] ; then
244		echo "ppd/C/stp-${f}.ppd.gz"
245	    elif [[ -f ppd/C/stp-${f}.ppd.GZ ]] ; then
246		echo "ppd/C/stp-${f}.ppd.GZ"
247	    elif [[ -f ppd/C/stp-${f}.ppd.bz2 ]] ; then
248		echo "ppd/C/stp-${f}.ppd.bz2"
249	    elif [[ -f ppd/C/stp-${f}.ppd.BZ2 ]] ; then
250		echo "ppd/C/stp-${f}.ppd.BZ2"
251	    elif [[ -f ppd/C/stp-${f}.ppd.z ]] ; then
252		echo "ppd/C/stp-${f}.ppd.z"
253	    elif [[ -f ppd/C/stp-${f}.ppd.Z ]] ; then
254		echo "ppd/C/stp-${f}.ppd.Z"
255	    elif [[ -f ppd/C/stp-${f}.${version}.ppd ]] ; then
256		echo "ppd/C/stp-${f}.${version}.ppd"
257	    elif [[ -f ppd/C/stp-${f}.${version}.ppd.gz ]] ; then
258		echo "ppd/C/stp-${f}.${version}.ppd.gz"
259	    elif [[ -f ppd/C/stp-${f}.${version}.ppd.GZ ]] ; then
260		echo "ppd/C/stp-${f}.${version}.ppd.GZ"
261	    elif [[ -f ppd/C/stp-${f}.${version}.ppd.bz2 ]] ; then
262		echo "ppd/C/stp-${f}.${version}.ppd.bz2"
263	    elif [[ -f ppd/C/stp-${f}.${version}.ppd.BZ2 ]] ; then
264		echo "ppd/C/stp-${f}.${version}.ppd.BZ2"
265	    elif [[ -f ppd/C/stp-${f}.${version}.ppd.z ]] ; then
266		echo "ppd/C/stp-${f}.${version}.ppd.z"
267	    elif [[ -f ppd/C/stp-${f}.${version}.ppd.Z ]] ; then
268		echo "ppd/C/stp-${f}.${version}.ppd.Z"
269	    fi
270	done
271    else
272	echo ppd/C/*.ppd*
273    fi
274}
275
276if [[ $make_ppds -gt 0 || ! -d ppd/C ]] ; then
277    rm -rf ppd/C
278## not all systems can work with gzipped PPDs
279    extra_genppd_opts="$extra_genppd_opts ${printers_to_test[*]}"
280    if [[ $skip_simplified == 1 ]] ; then
281        echo make ppd-nonls EXTRA_GENPPD_OPTS="$extra_genppd_opts"
282        make ppd-nonls EXTRA_GENPPD_OPTS="$extra_genppd_opts"
283    else
284        make ppd-nonls-a EXTRA_GENPPD_OPTS="$extra_genppd_opts"
285    fi
286fi
287
288find_page_size() {
289    ppd=$1
290    (( use_smallest_pagesize == 0 )) && return;
291    driver=$(grep '^\*StpDriverName' "$ppd" | sed -e 's/^[^"]*"//' -e 's/"//g')
292    pagesize=$(./min-pagesize "$driver")
293    [[ -n "$pagesize" ]] && echo "PageSize=$pagesize"
294}
295
296find_resolution() {
297    ppd=$1
298    resolutions=$(grep "^\\*Resolution " "$ppd" |sed -e 's,/.*,,' -e 's/.* //')
299    [[ -z "$resolutions" ]] && return
300    low_resolution=9999999999
301    low_resolution_name=''
302    high_resolution=0
303    high_resolution_name=''
304    for r in $resolutions ; do
305	res=$(sed -e 's/dpi//' -e 's/x/ \\\* /' -e 's/^\([0-9]*\)$/\1 \\\* \1/' <<< "$r")
306	resnum=$(eval "expr $res")
307	if (( resnum > high_resolution )) ; then
308	    high_resolution=$resnum
309	    high_resolution_name=$r
310	fi
311	if (( resnum < low_resolution )) ; then
312	    low_resolution=$resnum
313	    low_resolution_name=$r
314	fi
315    done
316    if (( use_explicit_quality == 1 )) ; then
317	echo "Resolution=$low_resolution_name"
318    elif (( use_explicit_quality == 2 )) ; then
319	echo "Resolution=$high_resolution_name"
320    fi
321
322}
323
324find_quality_preset() {
325    ppd=$1
326    if (( use_explicit_quality == 1 )) ; then
327	for q in "${quality_presets[@]}" ; do
328	    if grep -q "^\\*StpQuality $q" "$ppd" ; then
329		echo "StpQuality=$q"
330		return
331	    fi
332	done
333    elif (( use_explicit_quality == 2 )) ; then
334	best_quality=''
335	for q in "${quality_presets[@]}" ; do
336	    grep -q "^\\*StpQuality $q" "$ppd" && best_quality=$q
337	done
338	[[ -n $best_quality ]] && echo "StpQuality=$best_quality"
339    fi
340}
341
342find_quality() {
343    ppd=$1
344    if [[ ! -r $ppd ]] ; then
345	echo "Can't find $ppd!" 1>&2
346	exit 1;
347    fi
348    (( use_explicit_quality == 0 )) && return
349    if grep -q '\*Resolution' "$ppd" ; then
350	find_resolution "$ppd"
351    else
352	find_quality_preset "$ppd"
353    fi
354}
355
356xgrep() {
357    pat=$1
358    file=$2
359    if [[ $file == *.gz ]] ; then
360	grep -E -m1 "$pat" "$file"
361    else
362	zgrep "$pat" "$file"
363    fi
364}
365
366runcmd() {
367    qualarg=$(find_quality "$PPD")
368    sizearg=$(find_page_size "$PPD")
369    a=(1 1 1 1)
370    qarg="$qualarg $sizearg $cupsargs"
371    if [[ -x $cgpdftoraster ]] ; then
372	# cgpdftoraster doesn't like arguments.  How rude.
373	$cgpdftoraster "${a[@]}" "" < "$tfile"
374    elif [[ -f $tfile && -x $gstoraster ]] ; then
375	$gstoraster "${a[@]}" "$qarg" < "$tfile"
376    elif [[ -f $tfile ]] ; then
377	$pstops "${a[@]}" $"qarg" < "$tfile"
378    elif [[ -x $pstoraster ]] ; then
379	$pdftops "${a[@]}" "$qarg" < "$tfile" | $pstops "${a[@]}" "$pages$qarg" | $pstoraster
380    elif [[ -x $gstoraster ]] ; then
381	$pdftops "${a[@]}" "$qarg" < "$tfile" | $gstoraster "${a[@]}" "$pages$qarg"
382    else
383	$imagetoraster "${a[@]}" "$qarg" < calibrate.ppm
384    fi
385}
386
387do_output() {
388    driver=$(xgrep '^\*StpDriverName:' "$PPD" |awk '{print $2}' | sed 's/"//g')
389    if [[ -n $outdir ]] ; then
390	cat > "$outdir/$driver.prn"
391	if [[ -n $md5dir ]] ; then
392	    md5sum < "$outdir/$driver.prn" | sed "s/-/\*$driver/" > "$md5dir/$driver.md5"
393	fi
394    elif [[ -n $md5dir ]] ; then
395	cat | md5sum | sed "s/-/\*$driver/" > "$md5dir/$driver.md5"
396    else
397	cat >/dev/null
398    fi
399}
400
401run_rastertogp() {
402    qualarg=$(find_quality "$PPD")
403    sizearg=$(find_page_size "$PPD")
404    vg="libtool --mode=execute valgrind $valopts --log-fd=3"
405    vg1="$vg --num-callers=50 --leak-check=yes --error-limit=no --error-exitcode=1"
406    rgpc="$rgp 1 1 1 1"
407    qarg="$qualarg $sizearg $cupsargs"
408    case "$valgrind" in
409	1) $vg1 -q $rgpc ;;
410	2) $vg1 --leak-resolution=high $rgpc "$qarg" ;;
411	3) $vg1 --leak-resolution=high --show-reachable=yes $rgpc "$qarg";;
412	4) $vg $rgpc "$qarg" ;;
413	5) cat ;;
414	6) cat > /dev/null ;;
415	*) $rgpc "$qarg"
416    esac
417}
418
419runme() {
420    f="$1"
421    m=${all_models[$f]}
422    p=${f#*stp-}
423    p=${p/${version}./}
424    export PPD=$f
425    if (( dontrun > 0 )) ; then
426	output="${p%.ppd*} ($m)"
427    elif [[ -n $outdir || -n $md5dir ]] ; then
428	output="${p%.ppd*} ($m)...$( (runcmd 2>/dev/null | run_rastertogp | do_output) 2>&1 3>&2)"
429    else
430	output="${p%.ppd*} ($m)...$( (runcmd 2>/dev/null | run_rastertogp >/dev/null) 2>&1 3>&2)"
431    fi
432    return $?
433}
434
435runall() {
436    jobs="${1:-1}"
437    rotor="${2:-0}"
438    shift 2
439    retval=0
440    jobno=0
441    for f in "$@" ; do
442	if (( jobno == rotor )) ; then
443	    runme "$f" || retval=1
444	    echo "$output"
445	    grep -q 'ERROR:' <<< "$output" && retval=1
446	fi
447	jobno=$(((jobno+1) % jobs))
448    done
449    return $retval
450}
451
452get_models() {
453    re='\*StpDriverModelFamily:	'
454    if (( ${#all_models[*]} <= 1 )) ; then
455	declare -a models=($(xargs grep -m1 -H "^$re" <<< "$*" | sed "s/:$re/=/"))
456	for m in "${models[@]}" ; do
457	    model=${m#*=}
458	    file=${m%%=*}
459	    all_models[$file]=$model
460	done
461    fi
462}
463
464retval=0
465if [[ -d ppd/C ]] ; then
466    declare -a files=($(get_ppds))
467    declare -A models
468    declare -a nfiles
469    if (( skip_simplified > 0 )) ; then
470	for f in "${files[@]}" ; do
471	    [[ $f != *.sim.ppd* ]] && nfiles+=("$f")
472	done
473	files=("${nfiles[@]}")
474    fi
475    get_models "${files[@]}"
476    if [[ -n $family ]] ; then
477	nfiles=()
478	for f in "${files[@]}" ; do
479	    [[ ${all_models[$f]} =~ $family ]] && nfiles+=("$f")
480	done
481	files=("${nfiles[@]}")
482    fi
483    if (( single != 0 )) ; then
484	declare -A seen_models
485	nfiles=()
486	get_models "${files[@]}"
487	for f in "${files[@]}" ; do
488	    model=${all_models[$f]}
489	    [[ $f == *.sim.ppd ]] && model="${model}_sim"
490	    if [[ -z ${seen_models[$model]} ]] ; then
491		nfiles+=("$f")
492		seen_models[$model]=1
493	    fi
494	done
495	files=("${nfiles[@]}")
496    fi
497    (( ${#files[@]} < jobs )) && jobs=${#files[@]}
498    # Fire 'em off in the background...
499    for i in $(seq 0 $((jobs-1))) ; do
500	runall "$jobs" "$i" "${files[@]}" &
501    done
502    # And wait for them to complete.
503    for i in $(seq 0 $((jobs-1))) ; do
504	wait -n || retval=1
505    done
506fi
507
508[[ -n $tfile ]] && rm -f "$tfile"
509(( retval == 0 && make_ppds > 0 )) && rm -rf ppd/C && rmdir ppd
510exit "$retval"
511