From bc18dfff2f0a44461d6be2264511b3e8e63dbcac Mon Sep 17 00:00:00 2001 From: Sami Liedes Date: Fri, 11 Feb 2011 03:02:33 +0200 Subject: [PATCH] Automate test cases. This patch implements a framework for test cases. Each test case is compiled into an individual executable. All test cases can be run by invoking the command `make run_tests', which runs the shell script run_tests. Signed-off-by: Sami Liedes --- Makefile | 4 +- tests/.gitignore | 7 + tests/Makefile | 29 +++- tests/atmega48_disabled_timer.c | 11 +- tests/atmega48_enabled_timer.c | 39 +++++ tests/atmega88_example.c | 10 +- tests/atmega88_timer16.c | 24 +-- tests/atmega88_uart_echo.c | 10 +- tests/run_tests | 16 ++ tests/test_atmega48_disabled_timer.c | 18 ++ tests/test_atmega48_enabled_timer.c | 15 ++ tests/test_atmega48_watchdog_test.c | 15 ++ tests/test_atmega644_adc_test.c | 15 ++ tests/test_atmega88_example.c | 14 ++ tests/test_atmega88_timer16.c | 20 +++ tests/test_atmega88_uart_echo.c | 14 ++ tests/tests.c | 241 +++++++++++++++++++++++++++ tests/tests.h | 41 +++++ 18 files changed, 509 insertions(+), 34 deletions(-) create mode 100644 tests/.gitignore create mode 100644 tests/atmega48_enabled_timer.c create mode 100755 tests/run_tests create mode 100644 tests/test_atmega48_disabled_timer.c create mode 100644 tests/test_atmega48_enabled_timer.c create mode 100644 tests/test_atmega48_watchdog_test.c create mode 100644 tests/test_atmega644_adc_test.c create mode 100644 tests/test_atmega88_example.c create mode 100644 tests/test_atmega88_timer16.c create mode 100644 tests/test_atmega88_uart_echo.c create mode 100644 tests/tests.c create mode 100644 tests/tests.h diff --git a/Makefile b/Makefile index 65946e3..75fd39c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ +all: make-simavr make-tests - -all: make-tests +make-simavr: make -C simavr && make -C examples make-tests: diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..4147506 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,7 @@ +test_atmega48_disabled_timer +test_atmega48_enabled_timer +test_atmega48_watchdog_test +test_atmega644_adc_test +test_atmega88_example +test_atmega88_timer16 +test_atmega88_uart_echo diff --git a/tests/Makefile b/tests/Makefile index 0cf07f2..29f504f 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,7 +1,7 @@ # -# This makefile take each "at*" file, extracts it's part name -# And compile it into an ELF binary. -# It also disassemble it for debugging purposes. +# This makefile takes each "at*" file, extracts it's part name +# And compiles it into an ELF binary. +# It also disassembles it for debugging purposes. # # Copyright 2008, 2009 Michel Pollet # @@ -24,8 +24,16 @@ SHELL = /bin/bash sources := $(wildcard at*.c) +test_sources := $(wildcard test_*.c) +simavr = .. -all : ${sources:.c=.axf} +IPATH += ${simavr}/include +IPATH += ${simavr}/simavr/sim +CFLAGS += -Wall +TEST_SRC=${wildcard test_*.c} +TESTS=${TEST_SRC:.c=} + +all: obj ${sources:.c=.axf} ${TESTS} # if you want hex files #all : ${sources:.c=.hex} # if you need assembler output @@ -33,5 +41,14 @@ all : ${sources:.c=.axf} include ../Makefile.common -clean: - rm -f *.hex *.o *.axf *.s +# do not delete intermediate .o files after running `make run_tests' +.SECONDARY: + +test_%: ${OBJ}/test_%.o ${OBJ}/tests.o ${simavr}/simavr/${OBJ}/libsimavr.so + gcc ${LFLAGS} -o $@ $^ ${LDFLAGS} + +run_tests: obj ${sources:.c=.axf} ${TESTS} + ./run_tests + +clean: clean-${OBJ} + rm -f ${TESTS} *.axf diff --git a/tests/atmega48_disabled_timer.c b/tests/atmega48_disabled_timer.c index 2ccfaf8..332bbc9 100644 --- a/tests/atmega48_disabled_timer.c +++ b/tests/atmega48_disabled_timer.c @@ -14,8 +14,6 @@ AVR_MCU(F_CPU, "atmega48"); ISR(TIMER0_COMPA_vect) { - TCCR0B = 0; - TCNT0 = 0; } int main(void) @@ -25,10 +23,15 @@ int main(void) TIMSK0 |= (1 << OCIE0A); // Enable CTC interrupt OCR0A = 0xAA; // CTC compare value + //TCCR0B |= (1 << CS00) | (1 << CS01); // Start timer: clk/64 + sei(); // Enable global interrupts // here the interupts are enabled, but the interupt // vector should not be called - while(1) - sleep_mode(); + sleep_mode(); + + // this should not be reached + cli(); + sleep_mode(); } diff --git a/tests/atmega48_enabled_timer.c b/tests/atmega48_enabled_timer.c new file mode 100644 index 0000000..04b8db4 --- /dev/null +++ b/tests/atmega48_enabled_timer.c @@ -0,0 +1,39 @@ +/* + * avrtest.c + * + * Created on: 4 Feb 2011 + * Author: sliedes + * This is a very slightly modified version of atmega48_disabled_timer.c + * by jone. + */ + +#include +#include +#include + +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega48"); + +ISR(TIMER0_COMPA_vect) +{ +} + +int main(void) +{ + // Set up timer0 - do not start yet + TCCR0A |= (1 << WGM01); // Configure timer 0 for CTC mode + TIMSK0 |= (1 << OCIE0A); // Enable CTC interrupt + OCR0A = 0xAA; // CTC compare value + + TCCR0B |= (1 << CS00) | (1 << CS01); // Start timer: clk/64 + + sei(); // Enable global interrupts + + // here the interupts are enabled, but the interupt + // vector should not be called + sleep_mode(); + + // this should not be reached + cli(); + sleep_mode(); +} diff --git a/tests/atmega88_example.c b/tests/atmega88_example.c index 21dbd8c..75194b7 100644 --- a/tests/atmega88_example.c +++ b/tests/atmega88_example.c @@ -36,11 +36,11 @@ const struct avr_mmcu_vcd_trace_t _mytrace[] _MMCU_ = { uint32_t value EEMEM = 0xdeadbeef; static int uart_putchar(char c, FILE *stream) { - if (c == '\n') - uart_putchar('\r', stream); - loop_until_bit_is_set(UCSR0A, UDRE0); - UDR0 = c; - return 0; + if (c == '\n') + uart_putchar('\r', stream); + loop_until_bit_is_set(UCSR0A, UDRE0); + UDR0 = c; + return 0; } static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, diff --git a/tests/atmega88_timer16.c b/tests/atmega88_timer16.c index 3319625..f8e14f5 100644 --- a/tests/atmega88_timer16.c +++ b/tests/atmega88_timer16.c @@ -25,9 +25,9 @@ #include /* - * This demonstrate how to use the avr_mcu_section.h file + * This demonstrates how to use the avr_mcu_section.h file. * The macro adds a section to the ELF file with useful - * information for the simulator + * information for the simulator. */ #include "avr_mcu_section.h" AVR_MCU(F_CPU, "atmega88"); @@ -79,20 +79,20 @@ int main() // use CLK/8 prescale value, clear timer/counter on compareA match // toggle OC2A pin too TCCR2A = (1 << WGM21) | (1 << COM2A0); - TCCR2B = (2 << CS20); // prescaler - OCR2A = 63; // 64 hz - TIMSK2 |= (1 << OCIE2A); + TCCR2B = (2 << CS20); // prescaler + OCR2A = 63; // 64 hz + TIMSK2 |= (1 << OCIE2A); sei(); int count = 0; - while (count++ < 100) { - // we read TCNT1, which should contain some sort of incrementing value - tcnt = TCNT1; // read it - if (tcnt > 10000) { - TCNT1 = 500; // reset it arbitrarily - PORTB ^= 2; // mark it in the waveform file - } + while (count++ < 100) { + // we read TCNT1, which should contain some sort of incrementing value + tcnt = TCNT1; // read it + if (tcnt > 10000) { + TCNT1 = 500; // reset it arbitrarily + PORTB ^= 2; // mark it in the waveform file + } sleep_cpu(); // this will sleep until a new timer2 tick interrupt occurs } // sleeping with interrupt off is interpreted by simavr as "exit please" diff --git a/tests/atmega88_uart_echo.c b/tests/atmega88_uart_echo.c index 8d1adbe..b0ea04c 100644 --- a/tests/atmega88_uart_echo.c +++ b/tests/atmega88_uart_echo.c @@ -25,11 +25,11 @@ AVR_MCU(F_CPU, "atmega88"); AVR_MCU_SIMAVR_COMMAND(&GPIOR0); static int uart_putchar(char c, FILE *stream) { - if (c == '\r') - uart_putchar('\r', stream); - loop_until_bit_is_set(UCSR0A, UDRE0); - UDR0 = c; - return 0; + if (c == '\r') + uart_putchar('\r', stream); + loop_until_bit_is_set(UCSR0A, UDRE0); + UDR0 = c; + return 0; } static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, diff --git a/tests/run_tests b/tests/run_tests new file mode 100755 index 0000000..36becf0 --- /dev/null +++ b/tests/run_tests @@ -0,0 +1,16 @@ +#!/bin/sh + +TESTS=`find . -maxdepth 1 -executable -name test_\*` + +num_failed=0 +num_run=0 + +for test in $TESTS; do + num_run=$(($num_run+1)) + if ! $test; then + echo "$test returned with exit value $?." + num_failed=$(($num_failed+1)) + fi +done + +echo "Tests run: $num_run Successes: $(($num_run-$num_failed)) Failures: $num_failed" diff --git a/tests/test_atmega48_disabled_timer.c b/tests/test_atmega48_disabled_timer.c new file mode 100644 index 0000000..f4bd7ae --- /dev/null +++ b/tests/test_atmega48_disabled_timer.c @@ -0,0 +1,18 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + switch(tests_init_and_run_test("atmega48_disabled_timer.axf", 100000000)) { + case LJR_CYCLE_TIMER: + // the cycle timer fired + break; + case LJR_SPECIAL_DEINIT: + // sleep with interrupts off or some other such reason + fail("AVR woke up from sleep while it shouldn't have (after %" + PRI_avr_cycle_count " cycles)", tests_cycle_count); + default: + fail("Error in test case: Should never reach this."); + } + tests_success(); + return 0; +} diff --git a/tests/test_atmega48_enabled_timer.c b/tests/test_atmega48_enabled_timer.c new file mode 100644 index 0000000..22bade7 --- /dev/null +++ b/tests/test_atmega48_enabled_timer.c @@ -0,0 +1,15 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + switch(tests_init_and_run_test("atmega48_enabled_timer.axf", 100000000)) { + case LJR_CYCLE_TIMER: + fail("AVR did not wake up to the enabled timer."); + case LJR_SPECIAL_DEINIT: + break; + default: + fail("Error in test case: Should never reach this."); + } + tests_success(); + return 0; +} diff --git a/tests/test_atmega48_watchdog_test.c b/tests/test_atmega48_watchdog_test.c new file mode 100644 index 0000000..b2bc562 --- /dev/null +++ b/tests/test_atmega48_watchdog_test.c @@ -0,0 +1,15 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + + static const char *expected = + "Watchdog is active\r\n" + "Waiting for Watchdog to kick\r\n" + "Watchdog kicked us!\r\n"; + + tests_assert_uart_receive("atmega48_watchdog_test.axf", 1000000, + expected, '0'); + tests_success(); + return 0; +} diff --git a/tests/test_atmega644_adc_test.c b/tests/test_atmega644_adc_test.c new file mode 100644 index 0000000..a058f66 --- /dev/null +++ b/tests/test_atmega644_adc_test.c @@ -0,0 +1,15 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + + static const char *expected = + "Read 8 ADC channels to test interrupts\r\n" + "All done. Now reading the 1.1V value in pooling mode\r\n" + "Read ADC value 0155 = 1098 mvolts -- ought to be 1098\r\n"; + tests_assert_uart_receive("atmega644_adc_test.axf", 100000, + expected, '0'); + + tests_success(); + return 0; +} diff --git a/tests/test_atmega88_example.c b/tests/test_atmega88_example.c new file mode 100644 index 0000000..cafcf3e --- /dev/null +++ b/tests/test_atmega88_example.c @@ -0,0 +1,14 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + + static const char *expected = + "Read from eeprom 0xdeadbeef -- should be 0xdeadbeef\r\n" + "Read from eeprom 0xcafef00d -- should be 0xcafef00d\r\n"; + tests_assert_uart_receive("atmega88_example.axf", 100000, + expected, '0'); + + tests_success(); + return 0; +} diff --git a/tests/test_atmega88_timer16.c b/tests/test_atmega88_timer16.c new file mode 100644 index 0000000..0f2c895 --- /dev/null +++ b/tests/test_atmega88_timer16.c @@ -0,0 +1,20 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + enum tests_finish_reason reason = + tests_init_and_run_test("atmega88_timer16.axf", 10000000); + switch(reason) { + case LJR_CYCLE_TIMER: + fail("Test failed to finish properly; reason=%d, cycles=%" + PRI_avr_cycle_count, reason, tests_cycle_count); + break; + case LJR_SPECIAL_DEINIT: + break; + default: + fail("This should not be reached; reason=%d", reason); + } + tests_assert_cycles_between(12500000, 12500300); + tests_success(); + return 0; +} diff --git a/tests/test_atmega88_uart_echo.c b/tests/test_atmega88_uart_echo.c new file mode 100644 index 0000000..dc4640d --- /dev/null +++ b/tests/test_atmega88_uart_echo.c @@ -0,0 +1,14 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + + static const char *expected = + "Hey there, this should be received back\n" + "Received: Hey there, this should be received back\n"; + tests_assert_uart_receive("atmega88_uart_echo.axf", 100000, + expected, '0'); + + tests_success(); + return 0; +} diff --git a/tests/tests.c b/tests/tests.c new file mode 100644 index 0000000..44d70c7 --- /dev/null +++ b/tests/tests.c @@ -0,0 +1,241 @@ +#include "tests.h" +#include "sim_avr.h" +#include "sim_elf.h" +#include "sim_core.h" +#include "avr_uart.h" +#include +#include +#include +#include +#include +#include + +avr_cycle_count_t tests_cycle_count = 0; +int tests_disable_stdout = 1; + +static char *test_name = "(uninitialized test)"; +static FILE *orig_stderr = NULL; +static int finished = 0; + +static void atexit_handler(void) { + if (!finished) + _fail(NULL, 0, "Test exit without indicating success."); +} + +void tests_success(void) { + if (orig_stderr) + stderr = orig_stderr; + fprintf(stderr, "OK: %s\n", test_name); + finished = 1; +} + +void tests_init(int argc, char **argv) { + test_name = strdup(argv[0]); + atexit(atexit_handler); +} + +static avr_cycle_count_t +cycle_timer_longjmp_cb(struct avr_t *avr, avr_cycle_count_t when, void *param) { + jmp_buf *jmp = param; + longjmp(*jmp, LJR_CYCLE_TIMER); +} + +static jmp_buf *special_deinit_jmpbuf = NULL; + +static void special_deinit_longjmp_cb(struct avr_t *avr) { + if (special_deinit_jmpbuf) + longjmp(*special_deinit_jmpbuf, LJR_SPECIAL_DEINIT); +} + +static int my_avr_run(avr_t * avr) +{ + if (avr->state == cpu_Stopped) + return avr->state; + + uint16_t new_pc = avr->pc; + + if (avr->state == cpu_Running) + new_pc = avr_run_one(avr); + + // if we just re-enabled the interrupts... + // double buffer the I flag, to detect that edge + if (avr->sreg[S_I] && !avr->i_shadow) + avr->pending_wait++; + avr->i_shadow = avr->sreg[S_I]; + + // run the cycle timers, get the suggested sleep time + // until the next timer is due + avr_cycle_count_t sleep = avr_cycle_timer_process(avr); + + avr->pc = new_pc; + + if (avr->state == cpu_Sleeping) { + if (!avr->sreg[S_I]) { + printf("simavr: sleeping with interrupts off, quitting gracefully\n"); + avr_terminate(avr); + fail("Test case error: special_deinit() returned?"); + exit(0); + } + /* + * try to sleep for as long as we can (?) + */ + // uint32_t usec = avr_cycles_to_usec(avr, sleep); + // printf("sleep usec %d cycles %d\n", usec, sleep); + // usleep(usec); + avr->cycle += 1 + sleep; + } + // Interrupt servicing might change the PC too, during 'sleep' + if (avr->state == cpu_Running || avr->state == cpu_Sleeping) + avr_service_interrupts(avr); + + // if we were stepping, use this state to inform remote gdb + + return avr->state; +} + +avr_t *tests_init_avr(const char *elfname) { + tests_cycle_count = 0; + if (tests_disable_stdout) { + orig_stderr = stderr; + fclose(stdout); + stderr = stdout; + } + elf_firmware_t fw; + if (elf_read_firmware(elfname, &fw)) + fail("Failed to read ELF firmware \"%s\"", elfname); + avr_t *avr = avr_make_mcu_by_name(fw.mmcu); + if (!avr) + fail("Creating AVR failed."); + avr_init(avr); + avr_load_firmware(avr, &fw); + return avr; +} + +int tests_run_test(avr_t *avr, unsigned long run_usec) { + if (!avr) + fail("Internal test error: avr == NULL in run_test()"); + // register a cycle timer to fire after 100 seconds (simulation time); + // assert that the simulation has not finished before that. + jmp_buf jmp; + special_deinit_jmpbuf = &jmp; + avr->special_deinit = special_deinit_longjmp_cb; + avr_cycle_timer_register_usec(avr, run_usec, + cycle_timer_longjmp_cb, &jmp); + int reason = setjmp(jmp); + tests_cycle_count = avr->cycle; + if (reason == 0) { + // setjmp() returned directly, run avr + while (1) + my_avr_run(avr); + } else if (reason == 1) { + // returned from longjmp(); cycle timer fired + return reason; + } else if (reason == 2) { + // returned from special deinit, avr stopped + return reason; + } + fail("Error in test case: Should never reach this."); + return 0; +} + +int tests_init_and_run_test(const char *elfname, unsigned long run_usec) { + avr_t *avr = tests_init_avr(elfname); + return tests_run_test(avr, run_usec); +} + +struct output_buffer { + char *str; + int currlen; + int alloclen; + int maxlen; +}; + +/* static void buf_output_cb(avr_t *avr, avr_io_addr_t addr, uint8_t v, */ +/* void *param) { */ +static void buf_output_cb(struct avr_irq_t *irq, uint32_t value, void *param) { + struct output_buffer *buf = param; + if (!buf) + fail("Internal error: buf == NULL in buf_output_cb()"); + if (buf->currlen > buf->alloclen-1) + fail("Internal error"); + if (buf->alloclen == 0) + fail("Internal error"); + if (buf->currlen == buf->alloclen-1) { + buf->alloclen *= 2; + buf->str = realloc(buf->str, buf->alloclen); + } + buf->str[buf->currlen++] = value; + buf->str[buf->currlen] = 0; +} + +static void init_output_buffer(struct output_buffer *buf) { + buf->str = malloc(128); + buf->str[0] = 0; + buf->currlen = 0; + buf->alloclen = 128; + buf->maxlen = 4096; +} + +void tests_assert_uart_receive(const char *elfname, + unsigned long run_usec, + const char *expected, + char uart) { + avr_t *avr = tests_init_avr(elfname); + struct output_buffer buf; + init_output_buffer(&buf); + + avr_irq_register_notify(avr_io_getirq(avr, AVR_IOCTL_UART_GETIRQ(uart), UART_IRQ_OUTPUT), + buf_output_cb, &buf); + enum tests_finish_reason reason = tests_run_test(avr, run_usec); + if (reason == LJR_CYCLE_TIMER) { + if (strcmp(buf.str, expected) == 0) { + _fail(NULL, 0, "Simulation did not finish within %lu simulated usec. " + "UART output is correct and complete.", run_usec); + } + _fail(NULL, 0, "Simulation did not finish within %lu simulated usec. " + "UART output so far: \"%s\"", run_usec, buf.str); + } + if (strcmp(buf.str, expected) != 0) + _fail(NULL, 0, "UART outputs differ: expected \"%s\", got \"%s\"", expected, buf.str); +} + +void tests_assert_cycles_at_least(unsigned long n) { + if (tests_cycle_count < n) + _fail(NULL, 0, "Program ran for too few cycles (%" + PRI_avr_cycle_count " < %lu)", tests_cycle_count, n); +} + +void tests_assert_cycles_at_most(unsigned long n) { + if (tests_cycle_count > n) + _fail(NULL, 0, "Program ran for too many cycles (%" + PRI_avr_cycle_count " > %lu)", tests_cycle_count, n); +} + +void tests_assert_cycles_between(unsigned long min, unsigned long max) { + tests_assert_cycles_at_least(min); + tests_assert_cycles_at_most(max); +} + +void _fail(const char *filename, int linenum, const char *fmt, ...) { + if (orig_stderr) + stderr = orig_stderr; + + if (filename) + fprintf(stderr, "%s:%d: ", filename, linenum); + + fprintf(stderr, "Test "); + if (test_name) + fprintf(stderr, "%s ", test_name); + fprintf(stderr, "FAILED.\n"); + + if (filename) + fprintf(stderr, "%s:%d: ", filename, linenum); + + va_list va; + va_start(va, fmt); + vfprintf(stderr, fmt, va); + putc('\n', stderr); + + finished = 1; + _exit(1); +} diff --git a/tests/tests.h b/tests/tests.h new file mode 100644 index 0000000..14e435b --- /dev/null +++ b/tests/tests.h @@ -0,0 +1,41 @@ +#ifndef __TESTS_H__ +#define __TESTS_H__ + +#include "sim_avr.h" + +enum tests_finish_reason { + LJR_CYCLE_TIMER = 1, + LJR_SPECIAL_DEINIT = 2, + // LJR_SLEEP_WITH_INT_OFF - LJR_SPECIAL_DEINIT happens... +}; + +#define ATMEGA48_UDR0 0xc6 +#define ATMEGA88_UDR0 0xc6 +#define ATMEGA644_UDR0 0xc6 + +#define fail(s, ...) _fail(__FILE__, __LINE__, s, ## __VA_ARGS__) + +void __attribute__ ((noreturn,format (printf, 3, 4))) +_fail(const char *filename, int linenum, const char *fmt, ...); + +avr_t *tests_init_avr(const char *elfname); +void tests_init(int argc, char **argv); +void tests_success(void); + +int tests_run_test(avr_t *avr, unsigned long usec); +int tests_init_and_run_test(const char *elfname, unsigned long run_usec); +void tests_assert_uart_receive(const char *elfname, + unsigned long run_usec, + const char *expected, // what we should get + char uart); + +void tests_assert_cycles_at_least(unsigned long n); +void tests_assert_cycles_at_most(unsigned long n); + +// the range is inclusive +void tests_assert_cycles_between(unsigned long min, unsigned long max); + +extern avr_cycle_count_t tests_cycle_count; +extern int tests_disable_stdout; + +#endif -- 2.39.5