diff --git a/docs/app_nitz_test.md b/docs/app_nitz_test.md index c067128..678f7f0 100644 --- a/docs/app_nitz_test.md +++ b/docs/app_nitz_test.md @@ -1,72 +1,56 @@ -# 纯 APP NITZ 校时实机测试 +# 纯 APP:开机首次有效 NITZ 校时测试 -当前 `test/time-fix` 在 PDP 首次激活后自动启动 NITZ 测试,替换上一版自动 NTP 测试。只改 APP;本次没有修改、编译或烧录内核,不依赖之前添加的内核诊断接口。NTP 测试源文件保留但不参与当前 APP 构建。此模块不访问时间服务器域名或 UDP 123,使用设备已有的运营商 NITZ 查询接口。 +当前 `test/time-fix` 在 PDP 首次激活后启动一次独立测试任务,尽早读取有效的运营商 NITZ 快照,写入 APP RTC 后读回验证。不再要求两次查询值每隔 3 秒推进;这符合实机已观察到的“最近一次授时快照”行为。 -## 新增 SYS 读取探测 +只改 APP,没有修改内核,也不依赖新增的 `talkingq_time_diag_snapshot_v1`。前置 SYS/AT 探测及 APP NTP 测试均已关闭,相关源文件保留但不参与当前 APP 构建。原有内核功能不因本 APP 测试而关闭。 -当前版本在 NITZ 测试之前运行 `[APP-SYS-PROBE]`,只使用当前源码声明过的函数签名和 APP 已有 AT 接口,不读取旧 AXF,不修改内核,不调用 `talkingq_time_diag_snapshot_v1`。原有 TIME-DIAG 日志是否可用,不影响本探测。 +## 编译与实测 -- 动态查找 `PMIC_RTC_GetTime_Count` 和 `GetTimeZone`。如果原内核本来就导出这两个名字,调用 `SYS_OFFSET=0`,减去网络时区分钟数,间隔 3 秒读取两次,检查合理范围和推进量。找不到就输出 `direct_sys=unavailable`,不添加内核导出、不尝试固定地址。 -- 输出 `time(NULL)`、`ql_rtc_get_time_ex()` 作为对照,不把 `get_time_ex` 的名称当成读 SYS 的证据。 -- 通过 `ql_atcmd_send_sync` 连续两轮读取 `AT+QLTS=0`、`AT+QLTS=1`、`AT+QLTS=2`、`AT+CCLK?`。两轮间隔 3 秒,每个 AT 请求超时参数为 2 秒。记录返回码、运行计数和原始响应;本型号不支持时允许返回 ERROR。两个 QLTS 候选模式是否提供有效当前时间,需根据原始日志对照确认。 -- 本探测没有 RTC 设置调用。`direct_sys=candidate` 仅表示现有 SYS 函数可读、时间范围和推进正常,仍需与实际 UTC 核对;AT 返回成功也不等于已确认来源。 +在现有 Windows 环境执行 `build.bat app`,构建成功并核对 APP 镜像容量后,只更新 APP。保持原内核和故障现场 RTC/NVM 状态。 -刷入后提供从 `[APP-SYS-PROBE] begin` 到 `finished` 的完整输出。若 QLTS 返回正确且推进的当前时间,可进一步实现 APP 校时;若所有接口缺失/返回错误或只返回 APP 旧时间,该路线仍未得到可用依据。后续 NITZ 测试保持原有写入条件,快照冻结时不写 RTC。 - -## 编译和测试 - -在现有 Windows 环境执行 `build.bat app`,按现有流程更新 APP 镜像。保持原内核、原 RTC/NVM 状态。核对 APP 构建成功及镜像容量后启动设备,保留从开机到测试结束、再到一次实际 WSS 连接尝试的完整串口日志。 - -应看到: +启动时应出现: ```text -[TIME-DIAG] nitz_enabled=1 app_nitz_test=once_after_pdp ntp_test=off -[APP-SYS-PROBE] begin version=1 read_only=1 kernel_diag_used=0 -[APP-SYS-PROBE] exports sys_count=... network_zone=... -... -[APP-SYS-PROBE] finished rtc_not_written=1 -[APP-NITZ-TEST] begin version=2 source=operator_nitz ntp=off +[TIME-DIAG] nitz_enabled=1 app_nitz_test=first_valid_after_pdp ntp_test=off sys_probe=off +[APP-NITZ-TEST] begin version=3 source=operator_nitz ntp=off policy=first_valid_after_pdp sys_probe=off ``` -本机只进行了主机模拟测试和 ARM 目标语法检查,尚未完成 Windows 完整固件构建或实机测试。 - -## 实际流程 - -1. 检查原内核是否导出公开 SDK 接口 `ql_nw_get_nitz_time_info`、`ql_localtime_r`、`ql_rtc_set_time`、`ql_rtc_get_time`。 -2. 查询运营商 NITZ,输出完整原始字符串、返回值、`abs_time`、`leap_sec` 和查询耗时。支持 SDK 文档格式 `YY/MM/DD HH:MM:SS +32 00` 、紧凑格式 `YY/MM/DD,HH:MM:SS+32`,以及实机已返回的无正号、一位夏令时格式 `YY/MM/DD HH:MM:SS 32 0`。严格检查日期、闰年、时区及 UTC 范围(2024 年起,至 signed 32 位秒数上限)。不把语义未明确的 `abs_time` 当 Unix 时间戳使用。 -3. 间隔 3 秒再次查询。两次 UTC 必须向前推进,并与系统运行计数的增量相差不超过 1 秒,时区相同;单次查询超过 2 秒不采用。冻结的 NITZ 快照不写入 APP。最多尝试 3 组,组间等待 5 秒。 -4. 使用 NITZ 的 UT 日期字段生成 UTC 秒数。时区字段以 15 分钟为单位,只记录,不再次从 UT 扣除。这个解释依据仓库旧 SDK 镜像的 NITZ 转换逻辑:UT 日期转换后加时区生成 SYS 本地日历。原始值和解释会保留在日志中,供实机核对。 -5. 通过 SDK `ql_localtime_r` 按现有 PMIC 时区把 UTC 转成 APP RTC 日历,再调用 `ql_rtc_set_time`。不改时区、不操作 `app_ever` 或内部跟随标记、不直接写 NVM 文件。 -6. 读回 `ql_rtc_get_time` 日历和 `time(NULL)` 的 UTC 返回值,两者都必须与目标相差不超过 3 秒,并且 SDK 返回成功,才输出 `PASS`。设置后无论成功失败,都结束本次测试,避免反复写 RTC。 - -测试任务独立运行,不等待它就可以继续 MQTT 流程。每次启动完成一次测试;仅任务创建失败时允许下一次 PDP 激活重试。此版本没有周期重校、WSS 启动等待或主动重连逻辑,不是完整发布方案。 - -## 日志判断 - -以下是格式示例,不是设备实测结果: +保留从启动到 `[APP-NITZ-TEST] finished` 的完整日志,随后按语音键触发一次实际 WSS 连接,并保留连接日志。以下是预期格式示例,不是本次实机结果: ```text -[APP-NITZ-TEST] sample attempt=1 index=1 rc=0 raw="26/10/26 06:00:00 +32 00" abs_time=0 leap_sec=0 query_ms=... -[APP-NITZ-TEST] parsed utc=... zone_quarters=32 zone_minutes=480 -[APP-NITZ-TEST] freshness advance_s=3 elapsed_s=3 drift_s=0 -[APP-NITZ-TEST] set_begin app_before=... expected_utc=... pmic_zone_minutes=480 app_calendar=2026-10-26T14:00:03 -[APP-NITZ-TEST] readback set_rc=0 rtc_rc=0 app_after=... expected_utc=... delta_s=0 calendar=... +[APP-NITZ-TEST] sample attempt=1 index=1 rc=0 raw="26/10/26 08:00:10 32 0" abs_time=... leap_sec=0 query_ms=5 +[APP-NITZ-TEST] parsed utc=1793001610 zone_quarters=32 zone_minutes=480 +[APP-NITZ-TEST] snapshot_selected attempt=1 utc=1793001610 age=unknown first_valid_after_pdp=1 max_rtc_writes=1 +[APP-NITZ-TEST] set_begin app_before=... expected_utc=1793001610 pmic_zone_minutes=480 app_calendar=2026-10-26T16:00:10 +[APP-NITZ-TEST] readback set_rc=0 rtc_rc=0 app_after=1793001610 expected_utc=1793001610 delta_s=0 calendar=2026-10-26T16:00:10 [APP-NITZ-TEST] PASS source=nitz app_utc=verified wss_requires_separate_handshake_check=1 [APP-NITZ-TEST] finished one_test_per_boot=1 ``` -- `sdk_api_unavailable`:原内核缺少所需公开接口,本次不写 RTC。 -- `stage=parse`:时间为空、格式不支持或日期/范围无效。保留 `sample` 原文以便分析。 -- `stage=freshness`:两次时间冻结、跳变或与运行计时不符,不采用。若三组都失败,会输出 `no_fresh_nitz rtc_not_written=1`。 -- `stage=local_calendar`:SDK 时区转换未产生可用日历,不写 RTC。 -- `stage=app_clock_verification`:设置返回失败,或者读回 UTC/日历不符。需要分析是否有时区或 APP RTC 写入问题。 +## 校时逻辑 -`PASS` 只证明 NITZ 候选值推进正常、APP 写入和读回相符,无法单独证明运营商绝对时间准确或当前固件的 NITZ UT 语义与旧 SDK 完全一致。实机仍需把原始 NITZ、APP UTC 和实际北京时间对照,尤其检查整 8 小时偏差。它也不等于 WSS 握手或 OTA 下载成功;这两项继续看后续实际连接日志,TLS 校验保持开启。 +1. 检查原内核是否提供既有公开 SDK 接口 `ql_nw_get_nitz_time_info`、`ql_localtime_r`、`ql_rtc_set_time`、`ql_rtc_get_time`,缺失则退出。 +2. 立即读取 NITZ。支持实机格式 `YY/MM/DD HH:MM:SS 32 0`、文档中的带符号格式和紧凑格式。严格检查终止符、日期、闰年、时区和 UTC 范围:2024 年起至 signed 32 位秒数上限。 +3. 查询失败、空值、无效格式或查询耗时超过 2 秒,不写入,间隔 5 秒重试,最多 3 次。首次有效后立即采用,不等待第二个快照,也不运行前置 AT/SYS 探测。 +4. NITZ UT 日期字段按 UTC 解析;`32` 表示 32 个 15 分钟,即 UTC+8。UTC 时间戳本身不加 8 小时。使用 SDK `ql_localtime_r` 按现有 PMIC 时区转换成 RTC 本地日历,再调用 `ql_rtc_set_time`。不直接改时区、跟随标记或 NVM 文件。 +5. 读回 RTC 日历和 `time(NULL)` 返回的 UTC 秒数,要求 SDK 返回成功、日期有效,且 UTC 和本地日历均与写入目标相差不超过 3 秒,才输出 `PASS`。 +6. 设置尝试后不再重试写入,即使读回失败;任务结束后本次启动不再校时。仅任务创建失败允许下一次 PDP 激活重试创建。 -SDK 可能只提供 NITZ 最近一次通知的快照;如出现冻结拒绝,不应去掉新鲜度检查直接写入,应据日志评估带接收时刻的 NITZ 事件或已有业务服务器授时路径。本测试不注册新网络回调,不抢占现有网络事件处理。 +## 结果与边界 -## 主机回归 +- `sdk_api_unavailable`:缺少所需公开 SDK 接口。 +- `stage=parse` / `stage=slow_query`:本次查询未采用。 +- `no_valid_nitz rtc_not_written=1`:三次均未得到可采用值。 +- `snapshot_selected`:已选择本次启动首次读到的有效快照。 +- `stage=local_calendar`:SDK 时区转换未产生可用 RTC 日历,不写入。 +- `app_clock_verification`:设置或读回验证失败。 -`python3 tools/test_app_nitz_test.py` 编译实际 C 模块,模拟 API 缺失、查询失败、空串、无终止符、无效日期、冻结时间、UTC 跨天、时区转换、运行计数回绕、signed 32 位边界、首次查询失败恢复、写入失败、读回偏差及任务创建失败。启用 ASan/UBSan;不发送设备或公网数据。 +本版本按用户要求验证“开机联网后尽早采用首次有效快照”。首次读取不等于已证明基站刚刚下发;接口没有提供本测试可确认的快照接收时刻,所以记录 `age=unknown`。只补偿本次查询返回后至写入的运行时间,不把设备开机时间或 PDP 激活时间冒充快照接收时间。 -`python3 tools/test_app_sys_time_probe.py` 对实际 SYS/AT 探测模块验证 10 种场景:接口缺失、动态读取、无效/冻结时间、AT 错误、无终止符响应、tick 回绕等。测试不链接 RTC setter 或新增诊断接口;主机测试不证明本型号支持 QLTS。 +`PASS` 证明 APP 设置、时区换算和读回与所选快照一致,不独立证明快照绝对新鲜。需把原始 NITZ、APP 日历和实际北京时间对照,并继续看 WSS 实际握手。TLS 校验保持开启。本版没有增加 NITZ 更新事件处理、周期重校、WSS 启动等待或主动重连;先验证一次 APP 校时能否恢复当前故障设备。 + +## 本机验证 + +`python3 tools/test_app_nitz_test.py` 使用实际 C 源码验证解析、首次有效快照(含实机冻结快照)、失败后重试、最多一次写入、日期跨天、PMIC 时区转换、UTC 不重复偏移、tick 回绕、signed 32 位边界、设置失败及读回不符。启用 ASan/UBSan,不发送设备或公网数据。 + +同时对实际 SDK 头文件执行 ARM 目标语法检查。当前 macOS 环境不能运行仓库的 Windows 完整构建工具链,完整 APP 构建和实机验证待用户执行。SYS/AT 探测代码仍可通过 `tools/test_app_sys_time_probe.py` 单独回归,但不在本次开机流程中运行。 diff --git a/ql-application/threadx/interface/custom_app/Makefile b/ql-application/threadx/interface/custom_app/Makefile index 43ba0c7..817c87a 100644 --- a/ql-application/threadx/interface/custom_app/Makefile +++ b/ql-application/threadx/interface/custom_app/Makefile @@ -84,7 +84,6 @@ SRC_FILES+= \ mqtt_connect_diag.c \ time_diag.c \ app_nitz_test.c \ - app_sys_time_probe.c \ vad_det.c \ sleep_wake.c \ battery.c \ diff --git a/ql-application/threadx/interface/custom_app/app_nitz_test.c b/ql-application/threadx/interface/custom_app/app_nitz_test.c index 82269a0..559191c 100644 --- a/ql-application/threadx/interface/custom_app/app_nitz_test.c +++ b/ql-application/threadx/interface/custom_app/app_nitz_test.c @@ -7,14 +7,12 @@ #include "ql_nw.h" #include "ql_rtc.h" #include "app_nitz_test.h" -#include "app_sys_time_probe.h" /* SDK wrapper uses the same PMIC timezone as time(); no libc TZ assumptions. */ extern struct tm *ql_localtime_r(const time_t *, struct tm *); #define MIN_EPOCH 1704067200LL #define MAX_EPOCH 2147483647LL #define ATTEMPTS 3 -#define SAMPLE_MS 3000U static ql_task_t test_task; static int test_started; @@ -110,8 +108,11 @@ static int get_sample(int attempt, int index, nitz_sample *sample) printf("[APP-NITZ-TEST] rejected stage=parse reason=%d\r\n", parsed); return -1; } - /* A long blocking query makes the timestamp's age ambiguous. */ - if (sample->ticks - began > 400U) return -1; + /* Bound query delay; this does not establish the age of a cached NITZ. */ + if (sample->ticks - began > 400U) { + printf("[APP-NITZ-TEST] rejected stage=slow_query rtc_not_written=1\r\n"); + return -1; + } printf("[APP-NITZ-TEST] parsed utc=%ld zone_quarters=%d zone_minutes=%d\r\n", (long)sample->utc, sample->zone_quarters, sample->zone_quarters * 15); return 0; @@ -181,42 +182,31 @@ static int write_and_verify(const nitz_sample *reference) static void nitz_test_worker(void *arg) { - nitz_sample first, second; + nitz_sample sample; int attempt; - long long advance, elapsed, drift; - unsigned int span; (void)arg; - app_sys_time_probe_run(); - printf("[APP-NITZ-TEST] begin version=2 source=operator_nitz ntp=off\r\n"); + printf("[APP-NITZ-TEST] begin version=3 source=operator_nitz ntp=off " + "policy=first_valid_after_pdp sys_probe=off\r\n"); if (!m_get_api_ptr || !m_get_api_ptr("ql_nw_get_nitz_time_info") || !m_get_api_ptr("ql_localtime_r") || !m_get_api_ptr("ql_rtc_set_time") || !m_get_api_ptr("ql_rtc_get_time")) { printf("[APP-NITZ-TEST] FAIL stage=sdk_api_unavailable\r\n"); goto done; } + /* Device experiment: use the first valid operator snapshot promptly after + * PDP activation. A snapshot need not advance between API reads. Its actual + * network reception time is unknown; readback alone cannot establish age. */ for (attempt = 1; attempt <= ATTEMPTS; ++attempt) { - if (get_sample(attempt, 1, &first) == 0) { - ql_rtos_task_sleep_ms(SAMPLE_MS); - if (get_sample(attempt, 2, &second) == 0) { - span = second.ticks - first.ticks; /* unsigned handles tick wrap */ - elapsed = span / 200U; - advance = (long long)second.utc - first.utc; - drift = advance - elapsed; - printf("[APP-NITZ-TEST] freshness advance_s=%lld elapsed_s=%lld " - "drift_s=%lld\r\n", advance, elapsed, drift); - if (span >= 400U && span <= 2000U && advance > 0 && - drift >= -1 && drift <= 1 && - first.zone_quarters == second.zone_quarters) { - write_and_verify(&second); - goto done; /* Never repeat an uncertain RTC write. */ - } - printf("[APP-NITZ-TEST] rejected stage=freshness " - "cached_or_inconsistent_nitz=1 rtc_not_written=1\r\n"); - } + if (get_sample(attempt, 1, &sample) == 0) { + printf("[APP-NITZ-TEST] snapshot_selected attempt=%d utc=%ld " + "age=unknown first_valid_after_pdp=1 max_rtc_writes=1\r\n", + attempt, (long)sample.utc); + write_and_verify(&sample); + goto done; /* No repeated write, even on setter/readback failure. */ } if (attempt < ATTEMPTS) ql_rtos_task_sleep_ms(5000); } - printf("[APP-NITZ-TEST] FAIL stage=no_fresh_nitz rtc_not_written=1\r\n"); + printf("[APP-NITZ-TEST] FAIL stage=no_valid_nitz rtc_not_written=1\r\n"); done: printf("[APP-NITZ-TEST] finished one_test_per_boot=1\r\n"); ql_rtos_task_delete(NULL); diff --git a/ql-application/threadx/interface/custom_app/net_mgt.c b/ql-application/threadx/interface/custom_app/net_mgt.c index 89d2407..6c664ae 100644 --- a/ql-application/threadx/interface/custom_app/net_mgt.c +++ b/ql-application/threadx/interface/custom_app/net_mgt.c @@ -201,7 +201,7 @@ int app_net_mgt_init() // ql_rtos_semaphore_create(&g_ql_ntp_sem,0); ql_rtc_set_nitz_mode(1); - printf("[TIME-DIAG] nitz_enabled=1 app_nitz_test=once_after_pdp ntp_test=off\r\n"); + printf("[TIME-DIAG] nitz_enabled=1 app_nitz_test=first_valid_after_pdp ntp_test=off sys_probe=off\r\n"); talkingq_time_diag_log("nitz-enabled"); ql_task_t appTaskRef = NULL; diff --git a/tools/test_app_nitz_test.py b/tools/test_app_nitz_test.py index f8fc9cd..f1256ea 100644 --- a/tools/test_app_nitz_test.py +++ b/tools/test_app_nitz_test.py @@ -32,14 +32,13 @@ int ql_rtc_get_time(ql_rtc_time_t *); #include #include #include "APP_SOURCE" -static int mode, creates, queries, sets, probes; -void app_sys_time_probe_run(void) {++probes;} +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) {ticks+=ms/5;} +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")); @@ -59,10 +58,11 @@ int ql_nw_get_nitz_time_info(QL_NW_NITZ_TIME_INFO_T *info) { 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; /* would advance past signed32 limit */ - if (mode==18) value+=(queries%2==0 ? 60 : 0); /* discontinuity */ + 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; @@ -75,6 +75,7 @@ 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;} @@ -130,14 +131,22 @@ static void check_parser(void) { 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<=6) || mode==15 || mode==17 || mode==18 || mode==19 || mode==7 || mode==22) + if ((mode>=1 && mode<=5) || mode==7 || mode==15 || mode==17 || mode==19 || mode==25) assert(sets==0); else assert(sets==1); - assert(queries<=6); - assert(probes==(mode==19 ? 0 : 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')) @@ -149,18 +158,24 @@ int main(int argc,char **argv) { '-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_fresh_nitz', 3: 'FAIL stage=no_fresh_nitz', - 4: 'FAIL stage=no_fresh_nitz', 5: 'FAIL stage=no_fresh_nitz', - 6: 'cached_or_inconsistent_nitz=1', 7: 'FAIL stage=local_calendar', + 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=no_fresh_nitz', 16: 'PASS source=nitz', - 17: 'FAIL stage=no_fresh_nitz', 18: 'cached_or_inconsistent_nitz=1', - 19: 'FAIL stage=create_task', 20: 'FAIL stage=app_clock_verification', 21: 'PASS source=nitz', 22: 'FAIL stage=no_fresh_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 - if mode not in (0, 12, 13, 14, 16, 21): + 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: Gregorian/parser validation and 23 API/freshness/RTC scenarios (ASan/UBSan)') + print('PASS: parser validation and 26 first-snapshot/retry/timezone/RTC scenarios (ASan/UBSan)')