1 #include <errno.h>
2 #include <fcntl.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 #include <unistd.h>
8 
9 #include "test.h"
10 
11 const char *progname;
12 static const char *LIBSPECTRUM_MIN_VERSION = "0.4.0";
13 
14 typedef test_return_t (*test_fn)( void );
15 
16 #ifndef O_BINARY
17 #define O_BINARY 0
18 #endif
19 
20 int
read_file(libspectrum_byte ** buffer,size_t * length,const char * filename)21 read_file( libspectrum_byte **buffer, size_t *length, const char *filename )
22 {
23   int fd;
24   struct stat info;
25   ssize_t bytes;
26 
27   fd = open( filename, O_RDONLY | O_BINARY );
28   if( fd == -1 ) {
29     fprintf( stderr, "%s: couldn't open `%s': %s\n", progname, filename,
30 	     strerror( errno ) );
31     return errno;
32   }
33 
34   if( fstat( fd, &info ) ) {
35     fprintf( stderr, "%s: couldn't stat `%s': %s\n", progname, filename,
36 	     strerror( errno ) );
37     return errno;
38   }
39 
40   *length = info.st_size;
41   *buffer = libspectrum_malloc( *length );
42 
43   bytes = read( fd, *buffer, *length );
44   if( bytes == -1 ) {
45     fprintf( stderr, "%s: error reading from `%s': %s\n", progname, filename,
46 	     strerror( errno ) );
47     return errno;
48   } else if( bytes < *length ) {
49     fprintf( stderr, "%s: read only %lu of %lu bytes from `%s'\n", progname,
50 	     (unsigned long)bytes, (unsigned long)*length, filename );
51     return 1;
52   }
53 
54   if( close( fd ) ) {
55     fprintf( stderr, "%s: error closing `%s': %s\n", progname, filename,
56 	     strerror( errno ) );
57     return errno;
58   }
59 
60   return 0;
61 }
62 
63 static test_return_t
load_tape(libspectrum_tape ** tape,const char * filename,libspectrum_error expected_result)64 load_tape( libspectrum_tape **tape, const char *filename,
65            libspectrum_error expected_result )
66 {
67   libspectrum_byte *buffer = NULL;
68   size_t filesize = 0;
69 
70   if( read_file( &buffer, &filesize, filename ) ) return TEST_INCOMPLETE;
71 
72   *tape = libspectrum_tape_alloc();
73 
74   if( libspectrum_tape_read( *tape, buffer, filesize, LIBSPECTRUM_ID_UNKNOWN,
75 			     filename ) != expected_result ) {
76     fprintf( stderr, "%s: reading `%s' did not give expected result\n",
77 	     progname, filename );
78     libspectrum_tape_free( *tape );
79     libspectrum_free( buffer );
80     return TEST_INCOMPLETE;
81   }
82 
83   libspectrum_free( buffer );
84 
85   return TEST_PASS;
86 }
87 
88 static test_return_t
read_tape(const char * filename,libspectrum_error expected_result)89 read_tape( const char *filename, libspectrum_error expected_result )
90 {
91   libspectrum_tape *tape;
92   test_return_t r;
93 
94   r = load_tape( &tape, filename, expected_result );
95   if( r != TEST_PASS ) return r;
96 
97   if( libspectrum_tape_free( tape ) ) return TEST_INCOMPLETE;
98 
99   return TEST_PASS;
100 }
101 
102 static test_return_t
read_snap(const char * filename,const char * filename_to_pass,libspectrum_error expected_result)103 read_snap( const char *filename, const char *filename_to_pass,
104 	   libspectrum_error expected_result )
105 {
106   libspectrum_byte *buffer = NULL;
107   size_t filesize = 0;
108   libspectrum_snap *snap;
109 
110   if( read_file( &buffer, &filesize, filename ) ) return TEST_INCOMPLETE;
111 
112   snap = libspectrum_snap_alloc();
113 
114   if( libspectrum_snap_read( snap, buffer, filesize, LIBSPECTRUM_ID_UNKNOWN,
115 			     filename_to_pass ) != expected_result ) {
116     fprintf( stderr, "%s: reading `%s' did not give expected result\n",
117 	     progname, filename );
118     libspectrum_snap_free( snap );
119     libspectrum_free( buffer );
120     return TEST_INCOMPLETE;
121   }
122 
123   libspectrum_free( buffer );
124 
125   if( libspectrum_snap_free( snap ) ) return TEST_INCOMPLETE;
126 
127   return TEST_PASS;
128 }
129 
130 static test_return_t
play_tape(const char * filename)131 play_tape( const char *filename )
132 {
133   libspectrum_byte *buffer = NULL;
134   size_t filesize = 0;
135   libspectrum_tape *tape;
136   libspectrum_dword tstates;
137   int flags;
138 
139   if( read_file( &buffer, &filesize, filename ) ) return TEST_INCOMPLETE;
140 
141   tape = libspectrum_tape_alloc();
142 
143   if( libspectrum_tape_read( tape, buffer, filesize, LIBSPECTRUM_ID_UNKNOWN,
144 			     filename ) ) {
145     libspectrum_tape_free( tape );
146     libspectrum_free( buffer );
147     return TEST_INCOMPLETE;
148   }
149 
150   libspectrum_free( buffer );
151 
152   do {
153 
154     if( libspectrum_tape_get_next_edge( &tstates, &flags, tape ) ) {
155       libspectrum_tape_free( tape );
156       return TEST_INCOMPLETE;
157     }
158 
159   } while( !( flags & LIBSPECTRUM_TAPE_FLAGS_STOP ) );
160 
161   if( libspectrum_tape_free( tape ) ) return TEST_INCOMPLETE;
162 
163   return TEST_PASS;
164 }
165 
166 /* Specific tests begin here */
167 
168 /* Test for bugs #1479451 and #1706994: tape object incorrectly freed
169    after reading invalid tape */
170 static test_return_t
test_1(void)171 test_1( void )
172 {
173   return read_tape( STATIC_TEST_PATH( "invalid.tzx" ), LIBSPECTRUM_ERROR_UNKNOWN );
174 }
175 
176 /* Test for bugs #1720238: TZX turbo blocks with zero pilot pulses and
177    #1720270: freeing a turbo block with no data produces segfault */
178 static test_return_t
test_2(void)179 test_2( void )
180 {
181   libspectrum_byte *buffer = NULL;
182   size_t filesize = 0;
183   libspectrum_tape *tape;
184   const char *filename = STATIC_TEST_PATH( "turbo-zeropilot.tzx" );
185   libspectrum_dword tstates;
186   int flags;
187 
188   if( read_file( &buffer, &filesize, filename ) ) return TEST_INCOMPLETE;
189 
190   tape = libspectrum_tape_alloc();
191 
192   if( libspectrum_tape_read( tape, buffer, filesize, LIBSPECTRUM_ID_UNKNOWN,
193 			     filename ) ) {
194     libspectrum_tape_free( tape );
195     libspectrum_free( buffer );
196     return TEST_INCOMPLETE;
197   }
198 
199   libspectrum_free( buffer );
200 
201   if( libspectrum_tape_get_next_edge( &tstates, &flags, tape ) ) {
202     libspectrum_tape_free( tape );
203     return TEST_INCOMPLETE;
204   }
205 
206   if( flags ) {
207     fprintf( stderr, "%s: reading first edge of `%s' gave unexpected flags 0x%04x; expected 0x0000\n",
208 	     progname, filename, flags );
209     libspectrum_tape_free( tape );
210     return TEST_FAIL;
211   }
212 
213   if( tstates != 667 ) {
214     fprintf( stderr, "%s: first edge of `%s' was %d tstates; expected 667\n",
215 	     progname, filename, tstates );
216     libspectrum_tape_free( tape );
217     return TEST_FAIL;
218   }
219 
220   if( libspectrum_tape_free( tape ) ) return TEST_INCOMPLETE;
221 
222   return TEST_PASS;
223 }
224 
225 /* Test for bug #1725864: writing empty .tap file causes crash */
226 static test_return_t
test_3(void)227 test_3( void )
228 {
229   libspectrum_tape *tape;
230   libspectrum_byte *buffer = (libspectrum_byte*)1;
231   size_t length = 0;
232 
233   tape = libspectrum_tape_alloc();
234 
235   if( libspectrum_tape_write( &buffer, &length, tape, LIBSPECTRUM_ID_TAPE_TAP ) ) {
236     libspectrum_tape_free( tape );
237     return TEST_INCOMPLETE;
238   }
239 
240   /* `buffer' should now have been set to NULL */
241   if( buffer ) {
242     fprintf( stderr, "%s: `buffer' was not NULL after libspectrum_tape_write()\n", progname );
243     libspectrum_tape_free( tape );
244     return TEST_FAIL;
245   }
246 
247   if( libspectrum_tape_free( tape ) ) return TEST_INCOMPLETE;
248 
249   return TEST_PASS;
250 }
251 
252 /* Test for bug #1753279: invalid compressed file causes crash */
253 static test_return_t
test_4(void)254 test_4( void )
255 {
256   const char *filename = STATIC_TEST_PATH( "invalid.gz" );
257   return read_snap( filename, filename, LIBSPECTRUM_ERROR_UNKNOWN );
258 }
259 
260 /* Further test for bug #1753279: invalid compressed file causes crash */
261 static test_return_t
test_5(void)262 test_5( void )
263 {
264   return read_snap( STATIC_TEST_PATH( "invalid.gz" ), NULL, LIBSPECTRUM_ERROR_UNKNOWN );
265 }
266 
267 /* Test for bug #1753938: pointer wraparound causes segfault */
268 static test_return_t
test_6(void)269 test_6( void )
270 {
271   const char *filename = STATIC_TEST_PATH( "invalid.szx" );
272   return read_snap( filename, filename, LIBSPECTRUM_ERROR_CORRUPT );
273 }
274 
275 /* Test for bug #1755124: lack of sanity check in GDB code */
276 static test_return_t
test_7(void)277 test_7( void )
278 {
279   return read_tape( STATIC_TEST_PATH( "invalid-gdb.tzx" ), LIBSPECTRUM_ERROR_CORRUPT );
280 }
281 
282 /* Test for bug #1755372: empty DRB causes segfault */
283 static test_return_t
test_8(void)284 test_8( void )
285 {
286   return read_tape( STATIC_TEST_PATH( "empty-drb.tzx" ), LIBSPECTRUM_ERROR_NONE );
287 }
288 
289 /* Test for bug #1755539: problems with invalid archive info block */
290 static test_return_t
test_9(void)291 test_9( void )
292 {
293   return read_tape( STATIC_TEST_PATH( "invalid-archiveinfo.tzx" ), LIBSPECTRUM_ERROR_CORRUPT );
294 }
295 
296 /* Test for bug #1755545: invalid hardware info blocks can leak memory */
297 static test_return_t
test_10(void)298 test_10( void )
299 {
300   return read_tape( STATIC_TEST_PATH( "invalid-hardwareinfo.tzx" ), LIBSPECTRUM_ERROR_CORRUPT );
301 }
302 
303 /* Test for bug #1756375: invalid Warajevo tape block offset causes segfault */
304 static test_return_t
test_11(void)305 test_11( void )
306 {
307   return read_tape( STATIC_TEST_PATH( "invalid-warajevo-blockoffset.tap" ), LIBSPECTRUM_ERROR_CORRUPT );
308 }
309 
310 /* Test for bug #1757587: invalid custom info block causes memory leak */
311 static test_return_t
test_12(void)312 test_12( void )
313 {
314   return read_tape( STATIC_TEST_PATH( "invalid-custominfo.tzx" ), LIBSPECTRUM_ERROR_CORRUPT );
315 }
316 
317 /* Test for bug #1758860: loop end without a loop start block accesses
318    uninitialised memory */
319 static test_return_t
test_13(void)320 test_13( void )
321 {
322   libspectrum_byte *buffer = NULL;
323   size_t filesize = 0;
324   libspectrum_tape *tape;
325   const char *filename = STATIC_TEST_PATH( "loopend.tzx" );
326   libspectrum_dword tstates;
327   int flags;
328 
329   if( read_file( &buffer, &filesize, filename ) ) return TEST_INCOMPLETE;
330 
331   tape = libspectrum_tape_alloc();
332 
333   if( libspectrum_tape_read( tape, buffer, filesize, LIBSPECTRUM_ID_UNKNOWN,
334 			     filename ) ) {
335     libspectrum_tape_free( tape );
336     libspectrum_free( buffer );
337     return TEST_INCOMPLETE;
338   }
339 
340   libspectrum_free( buffer );
341 
342   if( libspectrum_tape_get_next_edge( &tstates, &flags, tape ) ) {
343     libspectrum_tape_free( tape );
344     return TEST_INCOMPLETE;
345   }
346 
347   if( libspectrum_tape_free( tape ) ) return TEST_INCOMPLETE;
348 
349   return TEST_PASS;
350 }
351 
352 /* Test for bug #1758860: TZX loop blocks broken */
353 static test_return_t
test_14(void)354 test_14( void )
355 {
356   return play_tape( STATIC_TEST_PATH( "loop.tzx" ) );
357 }
358 
359 /* Test for bug #1802607: TZX loop blocks still broken */
360 static test_return_t
test_16(void)361 test_16( void )
362 {
363   return play_tape( STATIC_TEST_PATH( "loop2.tzx" ) );
364 }
365 
366 /* Test for bug #1802618: TZX jump blocks broken */
367 static test_return_t
test_17(void)368 test_17( void )
369 {
370   return play_tape( STATIC_TEST_PATH( "jump.tzx" ) );
371 }
372 
373 /* Test for bug #1821425: crashes writing and reading empty CSW files */
374 static test_return_t
test_18(void)375 test_18( void )
376 {
377   return play_tape( STATIC_TEST_PATH( "empty.csw" ) );
378 }
379 
380 /* Test for bug #1828945: .tap writing code does not handle all block types */
381 static test_return_t
test_19(void)382 test_19( void )
383 {
384   libspectrum_byte *buffer = NULL;
385   size_t length = 0;
386   libspectrum_tape *tape;
387   const char *filename = DYNAMIC_TEST_PATH( "complete-tzx.tzx" );
388   test_return_t r;
389 
390   r = load_tape( &tape, filename, LIBSPECTRUM_ERROR_NONE );
391   if( r ) return r;
392 
393   if( libspectrum_tape_write( &buffer, &length, tape,
394                               LIBSPECTRUM_ID_TAPE_TAP ) ) {
395     fprintf( stderr, "%s: writing `%s' to a .tap file was not successful\n",
396              progname, filename );
397     libspectrum_tape_free( tape );
398     return TEST_INCOMPLETE;
399   }
400 
401   libspectrum_free( buffer );
402 
403   if( libspectrum_tape_free( tape ) ) return TEST_INCOMPLETE;
404 
405   return TEST_PASS;
406 }
407 
408 /* Tests for bug #1841085: SP not sanity checked when reading .sna files;
409    also tests bug #1841111: compressed snapshots cause segfault */
410 static test_return_t
test_20(void)411 test_20( void )
412 {
413   const char *filename = STATIC_TEST_PATH( "sp-2000.sna.gz" );
414   return read_snap( filename, filename, LIBSPECTRUM_ERROR_CORRUPT );
415 }
416 
417 static test_return_t
test_21(void)418 test_21( void )
419 {
420   const char *filename = STATIC_TEST_PATH( "sp-ffff.sna.gz" );
421   return read_snap( filename, filename, LIBSPECTRUM_ERROR_CORRUPT );
422 }
423 
424 /* Tests for bug #2002682: .mdr code does not correctly handle write protect
425    flag */
426 static test_return_t
test_22(void)427 test_22( void )
428 {
429   libspectrum_byte *buffer = NULL;
430   size_t filesize = 0;
431   libspectrum_microdrive *mdr;
432   const char *filename = STATIC_TEST_PATH( "writeprotected.mdr" );
433   test_return_t r;
434 
435   if( read_file( &buffer, &filesize, filename ) ) return TEST_INCOMPLETE;
436 
437   /* writeprotected.mdr deliberately includes an extra 0 on the end;
438      we want this in the buffer so we know what happens if we read off the
439      end of the file; however, we don't want it in the length */
440   filesize--;
441 
442   mdr = libspectrum_microdrive_alloc();
443 
444   if( libspectrum_microdrive_mdr_read( mdr, buffer, filesize ) ) {
445     libspectrum_microdrive_free( mdr );
446     libspectrum_free( buffer );
447     return TEST_INCOMPLETE;
448   }
449 
450   libspectrum_free( buffer );
451 
452   r = libspectrum_microdrive_write_protect( mdr ) ? TEST_PASS : TEST_FAIL;
453 
454   libspectrum_microdrive_free( mdr );
455 
456   return r;
457 }
458 
459 static test_return_t
test_23(void)460 test_23( void )
461 {
462   libspectrum_byte *buffer = NULL;
463   size_t filesize = 0, length;
464   libspectrum_microdrive *mdr;
465   const char *filename = STATIC_TEST_PATH( "writeprotected.mdr" );
466   test_return_t r;
467 
468   if( read_file( &buffer, &filesize, filename ) ) return TEST_INCOMPLETE;
469 
470   /* writeprotected.mdr deliberately includes an extra 0 on the end;
471      we want this in the buffer so we know what happens if we read off the
472      end of the file; however, we don't want it in the length */
473   filesize--;
474 
475   mdr = libspectrum_microdrive_alloc();
476 
477   if( libspectrum_microdrive_mdr_read( mdr, buffer, filesize ) ) {
478     libspectrum_microdrive_free( mdr );
479     libspectrum_free( buffer );
480     return TEST_INCOMPLETE;
481   }
482 
483   libspectrum_free( buffer ); buffer = NULL;
484 
485   libspectrum_microdrive_mdr_write( mdr, &buffer, &length );
486 
487   libspectrum_microdrive_free( mdr );
488 
489   r = ( length == filesize && buffer[ length - 1 ] == 1 ) ? TEST_PASS : TEST_FAIL;
490 
491   libspectrum_free( buffer );
492 
493   return r;
494 }
495 
496 static test_return_t
test_24(void)497 test_24( void )
498 {
499   const char *filename = DYNAMIC_TEST_PATH( "complete-tzx.tzx" );
500   libspectrum_byte *buffer;
501   size_t filesize;
502   libspectrum_tape *tape;
503   libspectrum_tape_iterator it;
504   libspectrum_tape_block *block;
505   libspectrum_dword expected_sizes[20] = {
506     15216886,	/* ROM */
507     3493371,	/* Turbo */
508     356310,	/* Pure tone */
509     1761,	/* Pulses */
510     1993724,	/* Pure data */
511     2163000,	/* Pause */
512     0,		/* Group start */
513     0,		/* Group end */
514     0,		/* Jump */
515     205434,	/* Pure tone */
516     0,		/* Loop start */
517     154845,	/* Pure tone */
518     0,		/* Loop end */
519     0,		/* Stop tape if in 48K mode */
520     0,		/* Comment */
521     0,		/* Message */
522     0,		/* Archive info */
523     0,		/* Hardware */
524     0,		/* Custom info */
525     771620,	/* Pure tone */
526   };
527   libspectrum_dword *next_size = &expected_sizes[ 0 ];
528   test_return_t r = TEST_PASS;
529 
530   if( read_file( &buffer, &filesize, filename ) ) return TEST_INCOMPLETE;
531 
532   tape = libspectrum_tape_alloc();
533 
534   if( libspectrum_tape_read( tape, buffer, filesize, LIBSPECTRUM_ID_UNKNOWN,
535 			     filename ) ) {
536     libspectrum_tape_free( tape );
537     libspectrum_free( buffer );
538     return TEST_INCOMPLETE;
539   }
540 
541   libspectrum_free( buffer );
542 
543   block = libspectrum_tape_iterator_init( &it, tape );
544 
545   while( block )
546   {
547     libspectrum_dword actual_size = libspectrum_tape_block_length( block );
548 
549     if( actual_size != *next_size )
550     {
551       fprintf( stderr, "%s: block had length %lu, but expected %lu\n", progname, (unsigned long)actual_size, (unsigned long)*next_size );
552       r = TEST_FAIL;
553       break;
554     }
555 
556     block = libspectrum_tape_iterator_next( &it );
557     next_size++;
558   }
559 
560   if( libspectrum_tape_free( tape ) ) return TEST_INCOMPLETE;
561 
562   return r;
563 }
564 
565 static test_return_t
test_25(void)566 test_25( void )
567 {
568   const char *filename = STATIC_TEST_PATH( "empty.z80" );
569   libspectrum_byte *buffer = NULL;
570   size_t filesize = 0, length = 0;
571   libspectrum_snap *snap;
572   int flags;
573   test_return_t r = TEST_INCOMPLETE;
574 
575   if( read_file( &buffer, &filesize, filename ) ) return TEST_INCOMPLETE;
576 
577   snap = libspectrum_snap_alloc();
578 
579   if( libspectrum_snap_read( snap, buffer, filesize, LIBSPECTRUM_ID_UNKNOWN,
580 			     filename ) != LIBSPECTRUM_ERROR_NONE ) {
581     fprintf( stderr, "%s: reading `%s' failed\n", progname, filename );
582     libspectrum_snap_free( snap );
583     libspectrum_free( buffer );
584     return TEST_INCOMPLETE;
585   }
586 
587   libspectrum_free( buffer );
588   buffer = NULL;
589 
590   if( libspectrum_snap_write( &buffer, &length, &flags, snap,
591                               LIBSPECTRUM_ID_SNAPSHOT_SNA, NULL, 0 ) !=
592       LIBSPECTRUM_ERROR_NONE ) {
593     fprintf( stderr, "%s: serialising to SNA failed\n", progname );
594     libspectrum_snap_free( snap );
595     return TEST_INCOMPLETE;
596   }
597 
598   libspectrum_snap_free( snap );
599   snap = libspectrum_snap_alloc();
600 
601   if( libspectrum_snap_read( snap, buffer, length, LIBSPECTRUM_ID_SNAPSHOT_SNA,
602                              NULL ) != LIBSPECTRUM_ERROR_NONE ) {
603     fprintf( stderr, "%s: restoring from SNA failed\n", progname );
604     libspectrum_snap_free( snap );
605     libspectrum_free( buffer );
606     return TEST_INCOMPLETE;
607   }
608 
609   libspectrum_free( buffer );
610 
611   if( libspectrum_snap_pc( snap ) != 0x1234 ) {
612     fprintf( stderr, "%s: PC is 0x%04x, not the expected 0x1234\n", progname,
613              libspectrum_snap_pc( snap ) );
614     r = TEST_FAIL;
615   } else if( libspectrum_snap_sp( snap ) != 0x8000 ) {
616     fprintf( stderr, "%s: SP is 0x%04x, not the expected 0x8000\n", progname,
617              libspectrum_snap_sp( snap ) );
618     r = TEST_FAIL;
619   } else {
620     r = TEST_PASS;
621   }
622 
623   libspectrum_snap_free( snap );
624 
625   return r;
626 }
627 
628 /* Tests for bug #3078262: last out to 0x1ffd is not serialised into .z80
629    files */
630 static test_return_t
test_26(void)631 test_26( void )
632 {
633   const char *filename = STATIC_TEST_PATH( "plus3.z80" );
634   libspectrum_byte *buffer = NULL;
635   size_t filesize = 0, length = 0;
636   libspectrum_snap *snap;
637   int flags;
638   test_return_t r = TEST_INCOMPLETE;
639 
640   if( read_file( &buffer, &filesize, filename ) ) return TEST_INCOMPLETE;
641 
642   snap = libspectrum_snap_alloc();
643 
644   if( libspectrum_snap_read( snap, buffer, filesize, LIBSPECTRUM_ID_UNKNOWN,
645 			     filename ) != LIBSPECTRUM_ERROR_NONE ) {
646     fprintf( stderr, "%s: reading `%s' failed\n", progname, filename );
647     libspectrum_snap_free( snap );
648     libspectrum_free( buffer );
649     return TEST_INCOMPLETE;
650   }
651 
652   libspectrum_free( buffer );
653   buffer = NULL;
654 
655   if( libspectrum_snap_write( &buffer, &length, &flags, snap,
656                               LIBSPECTRUM_ID_SNAPSHOT_Z80, NULL, 0 ) !=
657       LIBSPECTRUM_ERROR_NONE ) {
658     fprintf( stderr, "%s: serialising to Z80 failed\n", progname );
659     libspectrum_snap_free( snap );
660     return TEST_INCOMPLETE;
661   }
662 
663   libspectrum_snap_free( snap );
664   snap = libspectrum_snap_alloc();
665 
666   if( libspectrum_snap_read( snap, buffer, length, LIBSPECTRUM_ID_SNAPSHOT_Z80,
667                              NULL ) != LIBSPECTRUM_ERROR_NONE ) {
668     fprintf( stderr, "%s: restoring from Z80 failed\n", progname );
669     libspectrum_snap_free( snap );
670     libspectrum_free( buffer );
671     return TEST_INCOMPLETE;
672   }
673 
674   if( libspectrum_snap_out_plus3_memoryport( snap ) == 0xaa ) {
675     r = TEST_PASS;
676   } else {
677     fprintf( stderr,
678              "%s: Last out to 0x1ffd is 0x%02x, not the expected 0xaa\n",
679              progname, libspectrum_snap_out_plus3_memoryport( snap ) );
680     r = TEST_FAIL;
681   }
682 
683   libspectrum_snap_free( snap );
684 
685   return r;
686 }
687 
688 /* Tests for bug #2857419: SZX files were written with A and F reversed */
689 static test_return_t
test_27(void)690 test_27( void )
691 {
692   const char *filename = STATIC_TEST_PATH( "empty.szx" );
693   libspectrum_byte *buffer = NULL;
694   size_t filesize = 0;
695   libspectrum_snap *snap;
696   test_return_t r = TEST_INCOMPLETE;
697 
698   if( read_file( &buffer, &filesize, filename ) ) return TEST_INCOMPLETE;
699 
700   snap = libspectrum_snap_alloc();
701 
702   if( libspectrum_snap_read( snap, buffer, filesize, LIBSPECTRUM_ID_UNKNOWN,
703 			     filename ) != LIBSPECTRUM_ERROR_NONE ) {
704     fprintf( stderr, "%s: reading `%s' failed\n", progname, filename );
705     libspectrum_snap_free( snap );
706     libspectrum_free( buffer );
707     return TEST_INCOMPLETE;
708   }
709 
710   libspectrum_free( buffer );
711 
712   if( libspectrum_snap_a( snap ) != 0x12 ) {
713     fprintf( stderr, "%s: A is 0x%02x, not the expected 0x12\n", progname,
714              libspectrum_snap_a( snap ) );
715     r = TEST_FAIL;
716   } else if( libspectrum_snap_f( snap ) != 0x34 ) {
717     fprintf( stderr, "%s: F is 0x%02x, not the expected 0x34\n", progname,
718              libspectrum_snap_f( snap ) );
719     r = TEST_FAIL;
720   } else if( libspectrum_snap_a_( snap ) != 0x56 ) {
721     fprintf( stderr, "%s: A' is 0x%02x, not the expected 0x56\n", progname,
722              libspectrum_snap_a_( snap ) );
723     r = TEST_FAIL;
724   } else if( libspectrum_snap_f_( snap ) != 0x78 ) {
725     fprintf( stderr, "%s: F' is 0x%02x, not the expected 0x78\n", progname,
726              libspectrum_snap_f_( snap ) );
727     r = TEST_FAIL;
728   } else {
729     r = TEST_PASS;
730   }
731 
732   return r;
733 }
734 
735 struct test_description {
736 
737   test_fn test;
738   const char *description;
739   int active;
740 
741 };
742 
743 static struct test_description tests[] = {
744   { test_1, "Tape with unknown block", 0 },
745   { test_2, "TZX turbo data with zero pilot pulses and zero data", 0 },
746   { test_3, "Writing empty .tap file", 0 },
747   { test_4, "Invalid compressed file 1", 0 },
748   { test_5, "Invalid compressed file 2", 0 },
749   { test_6, "Pointer wraparound in SZX file", 0 },
750   { test_7, "Invalid TZX GDB", 0 },
751   { test_8, "Empty TZX DRB", 0 },
752   { test_9, "Invalid TZX archive info block", 0 },
753   { test_10, "Invalid hardware info block causes memory leak", 0 },
754   { test_11, "Invalid Warajevo tape file", 0 },
755   { test_12, "Invalid TZX custom info block causes memory leak", 0 },
756   { test_13, "TZX loop end block with loop start block", 0 },
757   { test_14, "TZX loop blocks", 0 },
758   { test_15, "Complete TZX file", 0 },
759   { test_16, "TZX loop blocks 2", 0 },
760   { test_17, "TZX jump blocks", 0 },
761   { test_18, "CSW empty file", 0 },
762   { test_19, "Complete TZX to TAP conversion", 0 },
763   { test_20, "SNA file with SP < 0x4000", 0 },
764   { test_21, "SNA file with SP = 0xffff", 0 },
765   { test_22, "MDR write protection 1", 0 },
766   { test_23, "MDR write protection 2", 0 },
767   { test_24, "Complete TZX timings", 0 },
768   { test_25, "Writing SNA file", 0 },
769   { test_26, "Writing +3 .Z80 file", 0 },
770   { test_27, "Reading old SZX file", 0 },
771 };
772 
773 static size_t test_count = sizeof( tests ) / sizeof( tests[0] );
774 
775 static void
parse_test_specs(char ** specs,int count)776 parse_test_specs( char **specs, int count )
777 {
778   int i, j;
779 
780   for( i = 0; i < count; i++ ) {
781 
782     const char *spec = specs[i];
783     const char *dash = strchr( spec, '-' );
784 
785     if( dash ) {
786       int begin = atoi( spec ), end = atoi( dash + 1 );
787       if( begin < 1 ) begin = 1;
788       if( end == 0 || end > test_count ) end = test_count;
789       for( j = begin; j <= end; j++ ) tests[j-1].active = 1;
790     } else {
791       int test = atoi( spec );
792       if( test < 1 || test > test_count ) continue;
793       tests[ test - 1 ].active = 1;
794     }
795 
796   }
797 }
798 
799 int
main(int argc,char * argv[])800 main( int argc, char *argv[] )
801 {
802   struct test_description *test;
803   size_t i;
804   int tests_done = 0, tests_skipped = 0;
805   int pass = 0, fail = 0, incomplete = 0;
806 
807   progname = argv[0];
808 
809   if( libspectrum_check_version( LIBSPECTRUM_MIN_VERSION ) ) {
810     if( libspectrum_init() ) return 2;
811   } else {
812     fprintf( stderr, "%s: libspectrum version %s found, but %s required",
813 	     progname, libspectrum_version(), LIBSPECTRUM_MIN_VERSION );
814     return 2;
815   }
816 
817   if( argc < 2 ) {
818     for( i = 0; i < test_count; i++ ) tests[i].active = 1;
819   } else {
820     parse_test_specs( &argv[1], argc - 1 );
821   }
822 
823   for( i = 0, test = tests;
824        i < test_count;
825        i++, test++ ) {
826     printf( "Test %d: %s... ", (int)i + 1, test->description );
827     if( test->active ) {
828       tests_done++;
829       switch( test->test() ) {
830       case TEST_PASS:
831 	printf( "passed\n" );
832 	pass++;
833 	break;
834       case TEST_FAIL:
835 	printf( "FAILED\n" );
836 	fail++;
837 	break;
838       case TEST_INCOMPLETE:
839 	printf( "NOT COMPLETE\n" );
840 	incomplete++;
841 	break;
842       }
843     } else {
844       tests_skipped++;
845       printf( "skipped\n" );
846     }
847 
848   }
849 
850   /* Stop silly divisions occuring */
851   if( !tests_done ) tests_done = 1;
852 
853   printf( "\n%3d tests run\n\n", (int)test_count );
854   printf( "%3d     passed (%6.2f%%)\n", pass, 100 * (float)pass/tests_done );
855   printf( "%3d     failed (%6.2f%%)\n", fail, 100 * (float)fail/tests_done );
856   printf( "%3d incomplete (%6.2f%%)\n", incomplete, 100 * (float)incomplete/tests_done );
857   printf( "%3d    skipped\n", tests_skipped );
858 
859   if( fail == 0 && incomplete == 0 ) {
860     return 0;
861   } else {
862     return 1;
863   }
864 }
865