182 lines
9.1 KiB
Python
182 lines
9.1 KiB
Python
"""Host behavioral tests of the actual APP module; no modem traffic is sent."""
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
APP = ROOT / 'ql-application/threadx/interface/custom_app'
|
|
with tempfile.TemporaryDirectory(prefix='app-nitz-test-') as directory:
|
|
work = Path(directory)
|
|
headers = {
|
|
'ql_rtos.h': '''#include <stddef.h>
|
|
typedef void *ql_task_t;
|
|
unsigned int ql_rtos_get_systicks(void);
|
|
void ql_rtos_task_sleep_ms(unsigned int);
|
|
int ql_rtos_task_create(ql_task_t *, unsigned int, unsigned char, char *, void (*)(void *), void *, ...);
|
|
int ql_rtos_task_delete(ql_task_t);
|
|
''',
|
|
'ql_func.h': '#include <stdint.h>\nextern intptr_t (*m_get_api_ptr)(char *);\n',
|
|
'ql_nw.h': '''enum { QL_NW_SUCCESS=0 };
|
|
typedef struct {char nitz_time[32]; unsigned long abs_time; unsigned char leap_sec;} QL_NW_NITZ_TIME_INFO_T;
|
|
int ql_nw_get_nitz_time_info(QL_NW_NITZ_TIME_INFO_T *);
|
|
''',
|
|
'ql_rtc.h': '''typedef struct {int tm_sec,tm_min,tm_hour,tm_mday,tm_mon,tm_year,tm_wday;} ql_rtc_time_t;
|
|
int ql_rtc_set_time(ql_rtc_time_t *);
|
|
int ql_rtc_get_time(ql_rtc_time_t *);
|
|
''',
|
|
}
|
|
for name, content in headers.items():
|
|
(work / name).write_text(content)
|
|
harness = r'''
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include "APP_SOURCE"
|
|
static int mode, creates, queries, sets, sleeps;
|
|
static unsigned int ticks=100;
|
|
static time_t app_epoch=100;
|
|
static int zone_minutes=480;
|
|
static ql_rtc_time_t saved;
|
|
unsigned int ql_rtos_get_systicks(void) {return ticks;}
|
|
void ql_rtos_task_sleep_ms(unsigned int ms) {assert(ms==5000); ++sleeps; ticks+=ms/5;}
|
|
static intptr_t lookup(char *name) {
|
|
assert(!strcmp(name,"ql_nw_get_nitz_time_info") || !strcmp(name,"ql_localtime_r") ||
|
|
!strcmp(name,"ql_rtc_set_time") || !strcmp(name,"ql_rtc_get_time"));
|
|
return mode==1 ? 0 : 1;
|
|
}
|
|
intptr_t (*m_get_api_ptr)(char *)=lookup;
|
|
int ql_nw_get_nitz_time_info(QL_NW_NITZ_TIME_INFO_T *info) {
|
|
time_t value=1793019600; /* 2026-10-26 13:00:00 UTC */
|
|
struct tm *utc;
|
|
++queries;
|
|
if (mode==2) return 1;
|
|
if (mode==3) return 0;
|
|
if (mode==4) {memset(info->nitz_time,'X',32); return 0;}
|
|
if (mode==5) {strcpy(info->nitz_time,"26/02/30 13:00:00 +32 00"); return 0;}
|
|
if (mode==6 || mode==22) {
|
|
strcpy(info->nitz_time,mode==22 ? "26/10/26 08:00:10 32 0" : "26/10/26 13:00:00 +32 00");
|
|
return 0;
|
|
}
|
|
if (mode==16 && queries==1) return 1;
|
|
if (mode==23 && queries<3) return 1;
|
|
if (mode==17) ticks+=600; /* slow query must be rejected */
|
|
if (mode==14) value=1793059198; /* near midnight UTC */
|
|
if (mode==15) value=2147483646; /* PMIC +8 local calendar exceeds signed32 range */
|
|
if (mode==18) value=1704067200; /* Old but valid snapshot: age cannot be proven. */
|
|
else value+=(ticks-100U)/200U;
|
|
/* Compute relative elapsed correctly in wrap scenario. */
|
|
if (mode==13) value=1793019600+(unsigned int)(ticks-(UINT32_MAX-199U))/200U;
|
|
utc=gmtime(&value); assert(utc);
|
|
snprintf(info->nitz_time,32,mode==21 ? "%02d/%02d/%02d %02d:%02d:%02d 32 0" : "%02d/%02d/%02d %02d:%02d:%02d +32 00",
|
|
utc->tm_year%100,utc->tm_mon+1,utc->tm_mday,utc->tm_hour,utc->tm_min,utc->tm_sec);
|
|
return 0;
|
|
}
|
|
struct tm *ql_localtime_r(const time_t *epoch,struct tm *out) {
|
|
time_t local=*epoch+zone_minutes*60;
|
|
struct tm *value;
|
|
if (mode==7) return NULL;
|
|
if (mode==25) local+=28800; /* Invalid PMIC conversion (+16 hours total). */
|
|
value=gmtime(&local); assert(value); *out=*value; return out;
|
|
}
|
|
time_t time(time_t *out) {(void)out; return app_epoch;}
|
|
int ql_rtc_set_time(ql_rtc_time_t *calendar) {
|
|
long long value;
|
|
++sets; saved=*calendar;
|
|
if (mode==8) return -1;
|
|
assert(calendar_epoch(calendar->tm_year,calendar->tm_mon,calendar->tm_mday,
|
|
calendar->tm_hour,calendar->tm_min,calendar->tm_sec,&value)==0);
|
|
if (mode!=9) app_epoch=(time_t)(value-zone_minutes*60);
|
|
if (mode==10) app_epoch+=28800; /* catches double timezone conversion */
|
|
return 0;
|
|
}
|
|
int ql_rtc_get_time(ql_rtc_time_t *out) {*out=saved; if (mode==20) out->tm_year=1970; return mode==11 ? -1 : 0;}
|
|
int ql_rtos_task_create(ql_task_t *task,unsigned int size,unsigned char priority,char *name,
|
|
void (*entry)(void *),void *arg,...) {
|
|
assert(size>=4096 && priority==110 && !strcmp(name,"app_nitz_test"));
|
|
++creates;
|
|
if (mode==19) return -1;
|
|
*task=(void *)1; entry(arg); return 0;
|
|
}
|
|
int ql_rtos_task_delete(ql_task_t task) {assert(task==NULL); return 0;}
|
|
static void check_parser(void) {
|
|
time_t epoch; int zone; long long value;
|
|
assert(parse_nitz("26/10/26 13:00:00 +32 00",&epoch,&zone)==0 &&
|
|
epoch==1793019600 && zone==32);
|
|
assert(parse_nitz("26/10/26,13:00:00-22",&epoch,&zone)==0 &&
|
|
epoch==1793019600 && zone==-22);
|
|
assert(parse_nitz("26/10/26 13:00:00 +56 02",&epoch,&zone)==0);
|
|
assert(parse_nitz("26/10/26 08:00:10 32 0",&epoch,&zone)==0 && epoch==1793001610 && zone==32);
|
|
assert(parse_nitz("26/10/26 13:00:00 -22 0",&epoch,&zone)==0 && zone==-22);
|
|
assert(parse_nitz("26/10/26 13:00:00 0 0",&epoch,&zone)==0 && zone==0);
|
|
assert(parse_nitz("26/10/26 13:00:00 0",&epoch,&zone)!=0);
|
|
assert(parse_nitz("26/10/26 13:00:00 320 0",&epoch,&zone)!=0);
|
|
assert(parse_nitz("24/02/29 23:59:59 +00 00",&epoch,&zone)==0);
|
|
assert(parse_nitz("26/02/29 00:00:00 +00 00",&epoch,&zone)!=0);
|
|
assert(parse_nitz("38/01/19 03:14:07 +00 00",&epoch,&zone)==0 && epoch==2147483647);
|
|
assert(parse_nitz("38/01/19 03:14:08 +00 00",&epoch,&zone)!=0);
|
|
const char *bad[]={"", "26/10/26 13:00:00", "23/12/31 23:59:59 +00 00",
|
|
"26/00/26 13:00:00 +00 00", "26/13/26 13:00:00 +00 00",
|
|
"26/10/00 13:00:00 +00 00", "26/04/31 13:00:00 +00 00",
|
|
"26/10/26 24:00:00 +00 00", "26/10/26 13:60:00 +00 00",
|
|
"26/10/26 13:00:60 +00 00", "26/10/26 13:00:00 +57 00",
|
|
"26/10/26 13:00:00 -49 00", "26/10/26 13:00:00 +32 03",
|
|
"26/10/26 13:00:00 +32 junk",
|
|
"26/10/26 13:00:00 +32 00junk", "x6/10/26 13:00:00 +32 00"};
|
|
for (unsigned int i=0;i<sizeof(bad)/sizeof(bad[0]);++i)
|
|
assert(parse_nitz(bad[i],&epoch,&zone)!=0);
|
|
assert(calendar_epoch(2024,2,29,23,59,59,&value)==0);
|
|
long long next;
|
|
assert(calendar_epoch(2024,3,1,0,0,0,&next)==0 && next-value==1);
|
|
}
|
|
int main(int argc,char **argv) {
|
|
assert(argc==2); mode=atoi(argv[1]); check_parser();
|
|
if (mode==12) zone_minutes=-330;
|
|
if (mode==24) zone_minutes=0;
|
|
if (mode==13) ticks=UINT32_MAX-199U;
|
|
app_nitz_test_start(); app_nitz_test_start();
|
|
assert(creates==(mode==19 ? 2 : 1));
|
|
if ((mode>=1 && mode<=5) || mode==7 || mode==15 || mode==17 || mode==19 || mode==25)
|
|
assert(sets==0);
|
|
else assert(sets==1);
|
|
if (mode==1 || mode==19) {assert(queries==0 && sleeps==0);}
|
|
else if ((mode>=2 && mode<=5) || mode==17 || mode==23) {assert(queries==3 && sleeps==2);}
|
|
else if (mode==16) {assert(queries==2 && sleeps==1);}
|
|
else {assert(queries==1 && sleeps==0);}
|
|
if (mode==22) {
|
|
assert(saved.tm_year==2026 && saved.tm_mon==10 && saved.tm_mday==26);
|
|
assert(saved.tm_hour==16 && saved.tm_min==0 && saved.tm_sec==10);
|
|
assert(app_epoch==1793001610); /* Local RTC +8, UTC remains unshifted. */
|
|
}
|
|
return 0;
|
|
}
|
|
'''.replace('APP_SOURCE', str(APP / 'app_nitz_test.c'))
|
|
(work / 'test.c').write_text(harness)
|
|
compiler = shutil.which('clang') or shutil.which('cc')
|
|
if not compiler:
|
|
raise SystemExit('A native C compiler is required')
|
|
subprocess.run([compiler, '-std=c99', '-Wall', '-Wextra', '-Werror',
|
|
'-fsanitize=address,undefined', '-I', str(work), '-I', str(APP / 'inc'),
|
|
str(work / 'test.c'), '-o', str(work / 'test')], check=True)
|
|
cases = {0: 'PASS source=nitz', 1: 'FAIL stage=sdk_api_unavailable',
|
|
2: 'FAIL stage=no_valid_nitz', 3: 'FAIL stage=no_valid_nitz',
|
|
4: 'FAIL stage=no_valid_nitz', 5: 'FAIL stage=no_valid_nitz',
|
|
6: 'PASS source=nitz', 7: 'FAIL stage=local_calendar',
|
|
8: 'FAIL stage=app_clock_verification', 9: 'FAIL stage=app_clock_verification',
|
|
10: 'FAIL stage=app_clock_verification', 11: 'FAIL stage=app_clock_verification',
|
|
12: 'PASS source=nitz', 13: 'PASS source=nitz', 14: 'PASS source=nitz',
|
|
15: 'FAIL stage=local_calendar', 16: 'PASS source=nitz',
|
|
17: 'FAIL stage=no_valid_nitz', 18: 'PASS source=nitz',
|
|
19: 'FAIL stage=create_task', 20: 'FAIL stage=app_clock_verification',
|
|
21: 'PASS source=nitz', 22: 'PASS source=nitz', 23: 'PASS source=nitz',
|
|
24: 'PASS source=nitz', 25: 'FAIL stage=local_calendar'}
|
|
for mode, expected in cases.items():
|
|
log = subprocess.check_output([str(work / 'test'), str(mode)], text=True)
|
|
assert expected in log, log
|
|
assert '[APP-SYS-PROBE]' not in log, log
|
|
assert 'stage=freshness' not in log, log
|
|
if expected.startswith('PASS'):
|
|
assert 'age=unknown first_valid_after_pdp=1 max_rtc_writes=1' in log, log
|
|
else:
|
|
assert '[APP-NITZ-TEST] PASS ' not in log, log
|
|
print('PASS: parser validation and 26 first-snapshot/retry/timezone/RTC scenarios (ASan/UBSan)')
|