3 # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2009, 2010, 2017 Dan McMahill
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of version 2 of the GNU General Public License as
7 # published by the Free Software Foundation
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # All rights reserved.
21 # This code was derived from code written by Dan McMahill as part of the
22 # latex-mk testsuite. The original code was covered by a BSD license
23 # but the copyright holder (Dan McMahill) has re-released the code under
26 magic_test_skip
=${PCB_MAGIC_TEST_SKIP:-no}
28 if test "x${magic_test_skip}" = "xyes" ; then
31 The environment variable PCB_MAGIC_TEST_SKIP is set to yes.
32 This causes the testsuite to skip all tests and report no errors.
33 This is used for certain debugging *only*. The primary use is to
34 allow testing of the 'distcheck' target without including the effects
35 of the testsuite. The reason this is useful is that due to minor differences
36 in library versions and perhaps roundoff in different CPU's, the testsuite
37 may falsely report failures on some systems. These reported failures
38 prevent using 'distcheck' for verifying the rest of the build system.
39 These comments only apply to the tests which try to compare image files
52 $0 -- Run pcb regression tests
55 $0 [-d | --debug] [-g | --golden dir] [-r|--regen] [testname1 [testname2[ ...]]]
59 The $0 script is used both for running the pcb regression testsuite
60 as well as maintaining it. The way the test suite works is a number
61 of different layouts are exported using the various export HID's.
63 The resulting output files are examined in various ways to make sure
64 they are correct. The exact details of how they are compared varies.
65 For example, the PNG outputs are compared using tools from the ImageMagick
66 suite while the ASCII centroid and bill of materials files are normalized
67 with awk and then compared with the standard diff utility. The normalization
68 removes things like a comment line which contains the creation date.
72 -d | --debug Enables extra debug output
74 -g | --golden <dir> : Specifies that <dir> should be used for the
77 -r | --regen : Specifies that the results should be taken as new
78 reference files instead of comparing them to the
83 - The GUI interface is not checked via the regression testsuite.
89 echo "----------------------------------------------------------------------"
92 ##########################################################################
99 if test $do_debug = yes ; then
106 ##########################################################################
108 # command line processing
129 # set the 'golden' directory.
135 # regenerate the 'golden' output files. Use this with caution.
136 # In particular, all differences should be noted and understood.
142 echo "unknown option: $1"
153 if test "X$regen" = "Xyes" && test $# -ne 1; then
154 echo "Please regenerate only one test at a time."
155 echo "This limitation is a safety measure."
159 ##########################################################################
161 # set up various tools
169 top_srcdir
=${top_srcdir:-.}
171 # The pcb wrapper script we want to test
173 # by default we will be running it from $(srcdir)/runs/testname
174 # so we need to look 3 levels up and then down to src
175 if test "X${WIN32}" = "Xyes" ; then
176 PCB
=${PCB:-../../../src/pcbtest.bat}
178 PCB
=${PCB:-../../../src/pcbtest.sh}
181 # The gerbv executible
182 GERBV
=${GERBV:-gerbv}
183 GERBV_DEFAULT_FLAGS
=${GERBV_DEFAULT_FLAGS:---export=png --window=640x480}
185 # various ImageMagick tools
186 IM_ANIMATE
=${IM_ANIMATE:-animate}
187 IM_COMPARE
=${IM_COMPARE:-compare}
188 IM_COMPOSITE
=${IM_COMPOSITE:-composite}
189 IM_CONVERT
=${IM_CONVERT:-convert}
190 IM_DISPLAY
=${IM_DISPLAY:-display}
191 IM_MONTAGE
=${IM_MONTAGE:-montage}
193 # xhost program to check for a working display
194 XHOST
=${XHOST:-xhost}
197 INDIR
=${INDIR:-${srcdir}/inputs}
199 REFDIR
=${REFDIR:-${srcdir}/golden}
204 # the list of tests to run
205 TESTLIST
=${srcdir}/tests.list
207 if test "X$regen" = "Xyes" ; then
211 # create output directory
212 if test ! -d $OUTDIR ; then
214 if test $?
-ne 0 ; then
215 echo "Failed to create output directory ${OUTDIR}"
220 if test ! -f ${TESTLIST} ; then
221 echo "ERROR: ($0) Test list $TESTLIST does not exist"
225 # Read the file, and remove any escaped newlines.
226 # Here's the low down on this sed program:
227 # :x create a label "x"
228 # /\\\\$/ match a "\" at the end of a line
229 # N add the next line to the buffer
230 # s/\\\\\n/ /g replace the escaped newlines with spaces
231 # bx jump back to label "x"
233 TESTFILESTRING
=`sed -e':x
239 if test -z "$all_tests" ; then
240 all_tests
=`echo "${TESTFILESTRING}" \
241 | ${AWK} 'BEGIN{FS="|"} /^#/{next} {print $1}'\
245 if test -z "${all_tests}" ; then
246 echo "$0: No tests specified"
249 echo "Tests to be executed: ${all_tests}"
253 # check if we have a working display. Without it we will
254 # fail the action script checks unless pcb was built as
255 # batch HID. I'm open to suggestions on improving
256 # the robustness of this check
258 echo "Checking for working X display"
259 ${XHOST} >/dev
/null
2>&1
261 if test $rc -eq 0 ; then
266 echo "no. Certain tests will be skipped."
269 # fail/pass/total counts
275 ##########################################################################
284 top_srcdir ${top_srcdir}
296 GERBV_DEFAULT_FLAGS ${GERBV_DEFAULT_FLAGS}
300 IM_ANIMATE ${IM_ANIMATE}
301 IM_COMPARE ${IM_COMPARE}
302 IM_COMPOSITE ${IM_COMPOSITE}
303 IM_CONVERT ${IM_CONVERT}
304 IM_DISPLAY ${IM_DISPLAY}
305 IM_MONTAGE ${IM_MONTAGE}
307 Working Display (for Action tests):
309 have_display ${have_display}
313 tmpd
=/tmp
/pcb_tests.$$
314 mkdir
-p -m 0700 $tmpd
316 if test $rc -ne 0 ; then
317 echo "$0: ERROR: could not create $tmpd"
321 ##########################################################################
323 # utility functions for comparison
327 # compare_check "test_name" "file1" "file2"
329 # Makes sure that file1 and file2 both exist. If not, mark the current
330 # test as skipped and give an error message
337 debug
"checking for $f1"
338 if test ! -f "$f1" ; then
339 echo "$0: ${fn}(): $f1 does not exist"
344 debug
"checking for $f2"
345 if test ! -f "$f2" ; then
346 echo "$0: ${fn}(): $f2 does not exist"
353 ##########################################################################
355 # ASCII file comparison routines
359 # run_diff "file1" "file2"
365 if test $?
-ne 0 ; then return 1 ; fi
373 compare_check
"compare_ascii" "$f1" "$f2" ||
return 1
374 run_diff
$f1 $f2 || test_failed
=yes
377 ##########################################################################
379 # BOM comparison routines
382 # used to remove things like creation date from BOM files
387 /^# Date:/ {print "# Date: today"; next}
388 /^# Author:/ {print "# Author: PCB"; next}
393 # top level function to compare BOM output
397 compare_check
"compare_bom" "$f1" "$f2" ||
return 1
399 # an example BOM file is:
401 # # PcbBOM Version 1.0
402 # # Date: Wed Jun 17 14:41:43 2009 UTC
403 # # Author: Dan McMahill
404 # # Title: Basic BOM/XY Test - PCB BOM
405 # # Quantity, Description, Value, RefDes
406 # # --------------------------------------------
407 # 8,"Standard SMT resistor, capacitor etc","RESC3216N",R90_TOP R180_TOP R270_TOP R0_TOP R270_BOT R180_BOT R90_BOT R0_BOT
408 # 8,"Dual in-line package, narrow (300 mil)","DIP8",UDIP90_TOP UDIP180_TOP UDIP270_TOP UDIP0_TOP UDIP270_BOT UDIP180_BOT UDIP90_BOT UDIP0_BOT
409 # 8,"Small outline package, narrow (150mil)","SO8",USO90_TOP USO180_TOP USO270_TOP USO0_TOP USO270_BOT USO180_BOT USO90_BOT USO0_BOT
411 # For comparison, we need to ignore changes in the Date and Author lines.
412 local cf1
=${tmpd}/`basename $f1`-ref
413 local cf2
=${tmpd}/`basename $f2`-out
415 normalize_bom
$f1 $cf1
416 normalize_bom
$f2 $cf2
417 run_diff
$cf1 $cf2 || test_failed
=yes
420 ##########################################################################
422 # X-Y (centroid) comparison routines
425 # used to remove things like creation date from BOM files
430 /^# Date:/ {print "# Date: today"; next}
431 /^# Author:/ {print "# Author: PCB"; next}
439 compare_check
"compare_xy" "$f1" "$f2" ||
return 1
441 local cf1
=${tmpd}/`basename $f1`-ref
442 local cf2
=${tmpd}/`basename $f2`-out
443 normalize_xy
"$f1" "$cf1"
444 normalize_xy
"$f2" "$cf2"
445 run_diff
"$cf1" "$cf2" || test_failed
=yes
448 ##########################################################################
450 # BOM_MD comparison routines
453 # used to remove things like creation date from BOM_MD files
458 /^Date:/ {print "Date: today"; next}
459 /^Author:/ {print "Author: PCB"; next}
464 # top level function to compare BOM_MD output
468 compare_check
"compare_bom_md" "$f1" "$f2" ||
return 1
470 # an example BOM_MD file is:
472 # # PCB Bill Of Materials MarkDown Version 1.0
474 # Date: zo 28 feb 2021 12:13:41 GMT UTC
476 # Author: Bert Timmerman
478 # Title: Basic BOM/XY Test
480 # | Quantity | Description | Value | RefDes |
481 # |----------|-------------|-------|--------|
482 # | 8 | Standard SMT resistor, capacitor etc | RESC3216N | R0_BOT R90_BOT R180_BOT R270_BOT R0_TOP R270_TOP R180_TOP R90_TOP |
483 # | 8 | Small outline package, narrow (150mil) | SO8 | USO0_BOT USO90_BOT USO180_BOT USO270_BOT USO0_TOP USO270_TOP USO180_TOP USO90_TOP |
484 # | 8 | Dual in-line package, narrow (300 mil) | DIP8 | UDIP0_BOT UDIP90_BOT UDIP180_BOT UDIP270_BOT UDIP0_TOP UDIP270_TOP UDIP180_TOP UDIP90_TOP |
486 # For comparison, we need to ignore changes in the Date and Author lines.
487 local cf1
=${tmpd}/`basename $f1`-ref
488 local cf2
=${tmpd}/`basename $f2`-out
490 normalize_bom_md
$f1 $cf1
491 normalize_bom_md
$f2 $cf2
492 run_diff
$cf1 $cf2 || test_failed
=yes
495 ##########################################################################
497 # GCODE comparison routines
500 # used to remove things like creation date from gcode files
504 # matches string such as '( Tue Mar 9 17:45:43 2010 )'
505 $AWK '/^\( *[A-Z][a-z][a-z] [A-Z][a-z][a-z] [0123 ][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9][0-9][0-9][0-9] *\)$/ {print "( Date String )"; next}
513 compare_check
"compare_gcode" "$f1" "$f2" ||
return 1
515 # For comparison, we need to ignore changes in the Date and Author lines.
516 local cf1
=${tmpd}/`basename $f1`-ref
517 local cf2
=${tmpd}/`basename $f2`-out
519 normalize_gcode
$f1 $cf1
520 normalize_gcode
$f2 $cf2
522 run_diff
"$cf1" "$cf2" || test_failed
=yes
525 ##########################################################################
527 # RS274-X and Excellon comparison
533 compare_check
"compare_rs274x" "$f1" "$f2" ||
return 1
535 # use gerbv to export our reference RS274-X file and our generated
536 # RS274-X file to png. Then we'll use ImageMagick to see
537 # if there are any differences in the resulting files
541 png1
=${pngdir}/${nb}-ref.png
542 png2
=${pngdir}/${nb}-out.png
544 debug
"${GERBV} ${GERBV_DEFAULT_FLAGS} --output=${png1} ${f1}"
545 ${GERBV} ${GERBV_DEFAULT_FLAGS} --output=${png1} ${f1}
547 debug
"${GERBV} ${GERBV_DEFAULT_FLAGS} --output=${png2} ${f2}"
548 ${GERBV} ${GERBV_DEFAULT_FLAGS} --output=${png2} ${f2}
550 compare_image
${png1} ${png2}
558 ##########################################################################
563 # used to remove things like job name and creation date from gsvit files
568 /<genTime>.*<\/genTime>/ {print "<genTime>today</genTime>"; next}
573 # top level function to compare gsvit output (*.xem)
577 compare_check
"compare_gsvit" "$f1" "$f2" ||
return 1
579 # For comparison, we need to ignore changes in the Date line.
580 local cf1
=${tmpd}/`basename $f1`-ref
581 local cf2
=${tmpd}/`basename $f2`-out
583 normalize_xem
$f1 $cf1
584 normalize_xem
$f2 $cf2
585 run_diff
$cf1 $cf2 || test_failed
=yes
588 ##########################################################################
590 # IPC-D-356 netlist comparison
593 # used to remove things like job name and creation date from IPC-D-356
595 normalize_ipcd356
() {
599 /^C IPC-D-356 Netlist generated by gEDA/ {print "C IPC-D-356 Netlist generated by gEDA PCB"; next}
600 /^C File created on/ {print "C File created on today"; next}
601 /^P JOB/ {print "P JOB PCB"; next}
606 # top level function to compare IPC-D-356 netlist output
610 compare_check
"compare_ipcd356" "$f1" "$f2" ||
return 1
612 # An example IPC-D-356 netlist file header is:
614 # C IPC-D-356 Netlist generated by gEDA PCB 1.99z
616 # C File created on Thu 01 Sep 2016 05:53:02 PM GMT UTC
618 # P JOB /home/bert/workspace/git/pcb/tests/inputs/ipcd356_board.pcb
626 # For comparison, we need to ignore changes in the Date and Job lines.
627 local cf1
=${tmpd}/`basename $f1`-ref
628 local cf2
=${tmpd}/`basename $f2`-out
630 normalize_ipcd356
$f1 $cf1
631 normalize_ipcd356
$f2 $cf2
632 run_diff
$cf1 $cf2 || test_failed
=yes
635 ##########################################################################
640 # used to remove things like job name and creation date from nelma files
644 # an example .em header has:
645 # /* Made with PCB Nelma export HID *//* Wed Apr 11 12:25:23 2018
647 /Made with PCB Nelma export HID/ {print"/* PCB Nelma Export *//* today"; next}
652 # top level function to compare gsvit output (*.xem)
656 compare_check
"compare_gsvit" "$f1" "$f2" ||
return 1
658 # For comparison, we need to ignore changes in the Date line.
659 local cf1
=${tmpd}/`basename $f1`-ref
660 local cf2
=${tmpd}/`basename $f2`-out
662 normalize_em
$f1 $cf1
663 normalize_em
$f2 $cf2
664 run_diff
$cf1 $cf2 || test_failed
=yes
667 ##########################################################################
669 # PostScript comparison
675 compare_check
"compare_ps" "$f1" "$f2" ||
return 1
677 # PostScript output is difficult to compare, because the last page
678 # ( = fab page) contains a date stamp written not as text, but drawn
679 # with lines. For now we only check whether the file contains valid
680 # PostScript and whether the page count matches.
682 echo "%!" > ${TEMP_FILE}
683 echo "currentpagedevice /PageCount get" >> ${TEMP_FILE}
684 echo " == flush" >> ${TEMP_FILE}
686 ORG_COUNT
=`gs -q -dNODISPLAY -dBATCH -dNOPAUSE "$f1" ${TEMP_FILE}`
687 NEW_COUNT
=`gs -q -dNODISPLAY -dBATCH -dNOPAUSE "$f2" ${TEMP_FILE}`
692 if test ${RESULT} -ne 0; then
693 echo "Invalid PostScript generated."
695 else if test "${ORG_COUNT}" != "${NEW_COUNT}"; then
696 echo -n "Page count of generated PostScript is ${NEW_COUNT} "
697 echo "instead of expected ${OLD_COUNT}."
701 unset ORG_COUNT NEW_COUNT RESULT
704 ##########################################################################
706 # GIF/JPEG/PNG comparison routines
712 compare_check
"compare_image" "$f1" "$f2" ||
return 1
714 # now see if the image files are the same
715 debug
"${IM_COMPARE} -metric MAE ${f1} ${f2} null:"
716 same
=`${IM_COMPARE} -metric MAE ${f1} ${f2} null: 2>&1 | \
717 ${AWK} '{if($1 == 0){print "yes"} else {print "no"}}'`
718 debug
"compare_image(): same = $same"
720 if test "$same" != yes ; then
722 echo "FAILED: See ${errdir}"
724 ${IM_COMPARE} ${f1} ${f2} ${errdir}/compare.png
725 ${IM_COMPOSITE} ${f1} ${f2} -compose difference ${errdir}/composite.png
726 ${IM_CONVERT} ${f1} ${f2} -compose difference -composite -colorspace gray ${errdir}/gray.png
727 cat > ${errdir}/animate.sh
<< EOF
729 ${IM_CONVERT} -label "%f" ${f1} ${f2} miff:- | \
730 ${IM_MONTAGE} - -geometry +0+0 -tile 1x1 miff:- | \
731 ${IM_ANIMATE} -delay 0.5 -loop 0 -
733 chmod a
+x
${errdir}/animate.sh
737 ##########################################################################
739 # PCB (layout) comparison routines
741 # used to remove things like creation date from BOM files
745 sed -e 's/#.*$//' -e '/^$/d' $f1 > $f2
753 compare_check
"compare_pcb" "$f1" "$f2" ||
return 1
755 # For comparison, we need to ignore changes in the Date and Author lines.
756 local cf1
=${tmpd}/`basename $f1`-ref
757 local cf2
=${tmpd}/`basename $f2`-out
759 normalize_pcb
$f1 $cf1
760 normalize_pcb
$f2 $cf2
763 run_diff
"$cf1" "$cf2" || test_failed
=yes
766 ##########################################################################
768 # The main program loop
771 for t
in $all_tests ; do
777 ######################################################################
779 # extract the details for the test
782 pcb_flags
="${PCB_DEFAULT_FLAGS}"
784 rundir
="${OUTDIR}/${t}"
785 refdir
="${REFDIR}/${t}"
786 errdir
=${rundir}/mismatch
788 # test_name | layout file(s) | [export hid name] | [optional arguments to pcb] | [mismatch]
790 # This string contains the line from the file with the fields for this test
791 TSTR
=`echo "${TESTFILESTRING}" | grep "^[ \t]*${t}[ \t]*|"`
794 name
=`echo ${TSTR} | $AWK 'BEGIN{FS="|"} {print $1}'`
795 files
=`echo ${TSTR} | $AWK 'BEGIN{FS="|"} {print $2}'`
796 hid
=`echo ${TSTR} | $AWK 'BEGIN{FS="|"} {gsub(/[ \t]*/, ""); print $3}'`
797 args
=`echo ${TSTR} | $AWK 'BEGIN{FS="|"} {print $4}'`
798 mismatch
=`echo ${TSTR} | $AWK 'BEGIN{FS="|"} {if($5 == "mismatch"){print "yes"}else{print "no"}}'`
799 out_files
=`echo ${TSTR} | $AWK 'BEGIN{FS="|"} {print $6}'`
801 # strip whitespace from single file names
802 while test "X${files# }" != "X${files#}" ; do
805 while test "X${files% }" != "X${files%}" ; do
809 if test "X${name}" = "X" ; then
810 echo "ERROR: Specified test ${t} does not appear to exist"
811 skip
=`expr $skip + 1`
815 if test "X${args}" != "X" ; then
819 if test "X${hid}" = "Xgerber" ; then
820 pcb_flags
="--fab-author Fab_Author ${pcb_flags}"
823 if test "X${hid}" = "Xaction" ; then
824 if test "X${have_display}" = "Xno" ; then
825 echo "Skipping action test because there is currently no X display available"
826 skip
=`expr $skip + 1`
830 # hidden here, still in effect for all HID tests
831 pcb_flags
="-x ${hid} ${pcb_flags}"
834 ######################################################################
836 # Set up the run directory
839 test -d ${rundir} && rm -fr ${rundir}
841 if test $?
-ne 0 ; then
842 echo "$0: Could not create run directory ${rundir}"
843 skip
=`expr $skip + 1`
848 ######################################################################
850 # check to see if the files we need exist and copy them to the run
857 if test ! -f ${INDIR}/${f} ; then
858 echo "ERROR: File $f specified as part of the $t test does not exist"
861 path_files
="${path_files} ${INDIR}/${f}"
862 cp "${INDIR}/${f}" "${rundir}"
863 chmod u
+w
"${rundir}/${f}"
867 if test "$missing_files" = "yes" ; then
868 echo "${t} had missing input files. Skipping test"
869 skip
=`expr $skip + 1`
873 ######################################################################
878 for f
in ${files} ; do
881 pcb_flags
="${pcb_flags} --action-script ${f}"
884 pcb_files
="${pcb_files} ${f}"
890 echo "\"$f\" is not a supported input file"
896 echo "(cd ${rundir} && ${PCB} ${pcb_flags} ${pcb_files})"
897 (cd ${rundir} && ${PCB} ${pcb_flags} ${pcb_files} 2> run_tests.err.log
)
900 if test $pcb_rc -ne 0 ; then
901 echo "${PCB} returned ${pcb_rc}. This is a failure."
902 fail
=`expr $fail + 1`
906 ######################################################################
911 if test "X$regen" = "Xyes" ; then
912 echo "Regenerated ${t}"
914 # compare the result to our reference file
917 for f
in $out_files ; do
918 debug
"processing $f"
919 # break apart type:fn into the type and file name
920 type=`echo $f | sed 's;:.*;;g'`
921 fn
=`echo $f | sed 's;.*:;;g'`
925 compare_ascii
${refdir}/${fn} ${rundir}/${fn}
928 fn1
=`echo ${fn} | sed 's/;.*//g'`
929 fn2
=`echo ${fn} | sed 's/.*;//g'`
930 compare_ascii
${rundir}/${fn1} ${rundir}/${fn2}
935 compare_bom
${refdir}/${fn} ${rundir}/${fn}
939 compare_xy
${refdir}/${fn} ${rundir}/${fn}
944 compare_bom_md
${refdir}/${fn} ${rundir}/${fn}
949 compare_gcode
${refdir}/${fn} ${rundir}/${fn}
954 compare_cnc
${refdir}/${fn} ${rundir}/${fn}
958 compare_rs274x
${refdir}/${fn} ${rundir}/${fn}
963 compare_nelma
${refdir}/${fn} ${rundir}/${fn}
968 compare_gsvit
${refdir}/${fn} ${rundir}/${fn}
973 compare_ipcd356
${refdir}/${fn} ${rundir}/${fn}
978 compare_ps
${refdir}/${fn} ${rundir}/${fn}
983 compare_image
${refdir}/${fn} ${rundir}/${fn}
987 compare_image
${refdir}/${fn} ${rundir}/${fn}
991 compare_image
${refdir}/${fn} ${rundir}/${fn}
996 compare_pcb
${refdir}/${fn} ${rundir}/${fn}
1001 echo "internal error: $type is not a known file type"
1008 if test "X${hid}" != "Xaction" ; then
1009 # clean up the input files we'd copied over
1010 for f
in $files ; do
1011 rm -f ${rundir}/${f}
1015 if test $test_failed = yes ; then
1017 fail
=`expr $fail + 1`
1018 elif test $test_skipped = yes ; then
1020 skip
=`expr $skip + 1`
1023 pass
=`expr $pass + 1`
1031 echo "Passed $pass, failed $fail, skipped $skip out of $tot tests."
1034 if test $fail -ne 0 ; then