From b4f6fea5af21894592c3549292603558098ddfe2 Mon Sep 17 00:00:00 2001 From: Yunlk <3283559503@qq.com> Date: Mon, 2 Nov 2026 16:07:43 +0800 Subject: [PATCH] [drivers][adc] test: add a framework testcase with a simulated device Part of #11972. ADC had neither a utest nor a simulated device, so the framework was never exercised in CI. The testcase registers its own simulated rt_adc_device, so no hardware and no QEMU peripheral model is required, and covers: adc_tc_register_test device class, ops pointer, name lookup adc_tc_enable_disable_test enabled() receives the right channel and flag adc_tc_read_test rt_adc_read() returns the raw value, and 0 when convert() fails instead of a stale value adc_tc_voltage_test scaling by resolution and vref, plus exactly one enable / convert / disable per call adc_tc_missing_hooks_test -RT_ENOSYS when the optional enabled() hook is absent The expected millivolt value is hardcoded rather than recomputed in the test: recomputing it with the same formula as the code under test would keep passing even when the formula is wrong. Verified by injecting a deliberate error (/ ((1 << res) - 2) instead of - 1): with raw = 2048 that mutation is invisible because both give 1650, so the case uses raw = 1000, where 805 and 806 differ. With the mutation in place the test fails as intended: [E/utest] at (adc_tc.c); func: (adc_tc_voltage_test:201); msg: ((rt_adc_voltage(dev, 3)) not == (805)) Verified on bsp/qemu-vexpress-a9 with RT_UTEST_ADC + RT_UTEST_USING_AUTO_RUN: [I/utest] utest unit name: (adc_tc_register_test) ... [I/utest] utest unit name: (adc_tc_missing_hooks_test) [I/utest] [ PASSED ] testcase (components.drivers.adc.framework) adc/SConscript gains the usual sub-directory traversal so that utest/ is picked up, matching what rtc/ and spi/ already do. Refs #11972 --- components/drivers/adc/SConscript | 6 + components/drivers/adc/utest/Kconfig | 8 + components/drivers/adc/utest/SConscript | 13 ++ components/drivers/adc/utest/adc_tc.c | 259 ++++++++++++++++++++++++ components/utilities/utest/Kconfig | 1 + 5 files changed, 287 insertions(+) create mode 100644 components/drivers/adc/utest/Kconfig create mode 100644 components/drivers/adc/utest/SConscript create mode 100644 components/drivers/adc/utest/adc_tc.c diff --git a/components/drivers/adc/SConscript b/components/drivers/adc/SConscript index 94101138f0e1..aaa5de33ff71 100644 --- a/components/drivers/adc/SConscript +++ b/components/drivers/adc/SConscript @@ -1,3 +1,4 @@ +import os from building import * cwd = GetCurrentDir() @@ -20,4 +21,9 @@ if GetDepend(['RT_USING_ADC_V2', 'RT_USING_FINSH', 'RT_ADC_V2_USING_MSH']): if len(src): group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_ADC'], CPPPATH = CPPPATH) +list = os.listdir(cwd) +for item in list: + if os.path.isfile(os.path.join(cwd, item, 'SConscript')): + group = group + SConscript(os.path.join(item, 'SConscript')) + Return('group') diff --git a/components/drivers/adc/utest/Kconfig b/components/drivers/adc/utest/Kconfig new file mode 100644 index 000000000000..f9b51381b7c6 --- /dev/null +++ b/components/drivers/adc/utest/Kconfig @@ -0,0 +1,8 @@ +menu "ADC Test" + +config RT_UTEST_ADC + bool "ADC framework Test" + default n + depends on RT_USING_ADC + +endmenu diff --git a/components/drivers/adc/utest/SConscript b/components/drivers/adc/utest/SConscript new file mode 100644 index 000000000000..242d67fa86ab --- /dev/null +++ b/components/drivers/adc/utest/SConscript @@ -0,0 +1,13 @@ +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() +src = [] +CPPPATH = [cwd] + +if GetDepend(['RT_UTEST_ADC']): + src += ['adc_tc.c'] + +group = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES'], CPPPATH = CPPPATH) + +Return('group') diff --git a/components/drivers/adc/utest/adc_tc.c b/components/drivers/adc/utest/adc_tc.c new file mode 100644 index 000000000000..da8f9d3b9fe5 --- /dev/null +++ b/components/drivers/adc/utest/adc_tc.c @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2006-2026 RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-11-02 Yunlk add ADC framework utest + */ + +/** + * Test Case Name: ADC Framework Test + * + * Test Objectives: + * - Verify that rt_hw_adc_register() installs the expected device class and ops. + * - Verify the enable/disable/convert/read paths reach the driver callbacks. + * - Verify rt_adc_read() returns 0 when convert fails. + * - Verify rt_adc_enable()/rt_adc_disable() return -RT_ENOSYS without an enabled() hook. + * - Verify rt_adc_voltage() scales the raw value by the resolution and vref. + * + * Dependencies: + * - Requires RT_UTEST_ADC and RT_USING_ADC. + * - Uses a simulated device, so no ADC hardware is needed. + * + * Expected Results: + * - All checks pass on any target that builds the ADC framework. + */ + +#include +#include +#include "utest.h" + +#ifdef RT_UTEST_ADC + +#define ADC_TC_NAME "adc_tc" +#define ADC_TC_CHANNEL 3 +#define ADC_TC_RESOLUTION 12 +#define ADC_TC_VREF 3300 /* millivolts */ +#define ADC_TC_RAW_VALUE 1000 +#define ADC_TC_EXPECTED_MV 805 /* 1000 * 3300 / ((1 << 12) - 1) = 805.8 */ + +/* Bookkeeping for the simulated device. */ +struct adc_tc_context +{ + int enable_count; + int disable_count; + int convert_count; + rt_bool_t enabled; + rt_uint32_t fail_convert; +}; + +static struct adc_tc_context adc_tc_ctx; +static struct rt_adc_device adc_tc_dev; + +static rt_err_t adc_tc_enabled(struct rt_adc_device *device, rt_int8_t channel, rt_bool_t enabled) +{ + struct adc_tc_context *ctx = device->parent.user_data; + + RT_ASSERT(channel == ADC_TC_CHANNEL); + + if (enabled) + { + ctx->enable_count++; + ctx->enabled = RT_TRUE; + } + else + { + ctx->disable_count++; + ctx->enabled = RT_FALSE; + } + + return RT_EOK; +} + +static rt_err_t adc_tc_convert(struct rt_adc_device *device, rt_int8_t channel, rt_uint32_t *value) +{ + struct adc_tc_context *ctx = device->parent.user_data; + + RT_ASSERT(channel == ADC_TC_CHANNEL); + + ctx->convert_count++; + + if (ctx->fail_convert) + { + return -RT_ERROR; + } + + *value = ADC_TC_RAW_VALUE; + + return RT_EOK; +} + +static rt_uint8_t adc_tc_get_resolution(struct rt_adc_device *device) +{ + return ADC_TC_RESOLUTION; +} + +static rt_int16_t adc_tc_get_vref(struct rt_adc_device *device) +{ + return ADC_TC_VREF; +} + +static const struct rt_adc_ops adc_tc_ops = +{ + .enabled = adc_tc_enabled, + .convert = adc_tc_convert, + .get_resolution = adc_tc_get_resolution, + .get_vref = adc_tc_get_vref, +}; + +/* A device without the optional hooks, used for the -ENOSYS paths. */ +static const struct rt_adc_ops adc_tc_ops_minimal = +{ + .convert = adc_tc_convert, +}; + +static void adc_tc_reset(void) +{ + rt_memset(&adc_tc_ctx, 0, sizeof(adc_tc_ctx)); + adc_tc_ctx.enabled = RT_FALSE; +} + +static void adc_tc_register_test(void) +{ + rt_device_t dev; + + adc_tc_reset(); + rt_memset(&adc_tc_dev, 0, sizeof(adc_tc_dev)); + + uassert_int_equal(rt_hw_adc_register(&adc_tc_dev, ADC_TC_NAME, &adc_tc_ops, &adc_tc_ctx), + RT_EOK); + + dev = rt_device_find(ADC_TC_NAME); + uassert_not_null(dev); + uassert_int_equal(dev->type, RT_Device_Class_ADC); + + /* the framework keeps the caller's ops */ + uassert_ptr_equal(adc_tc_dev.ops, &adc_tc_ops); +} + +static void adc_tc_enable_disable_test(void) +{ + rt_adc_device_t dev = (rt_adc_device_t)rt_device_find(ADC_TC_NAME); + + uassert_not_null(dev); + if (dev == RT_NULL) + { + return; + } + + adc_tc_reset(); + + uassert_int_equal(rt_adc_enable(dev, ADC_TC_CHANNEL), RT_EOK); + uassert_int_equal(adc_tc_ctx.enable_count, 1); + uassert_true(adc_tc_ctx.enabled == RT_TRUE); + + uassert_int_equal(rt_adc_disable(dev, ADC_TC_CHANNEL), RT_EOK); + uassert_int_equal(adc_tc_ctx.disable_count, 1); + uassert_true(adc_tc_ctx.enabled == RT_FALSE); +} + +static void adc_tc_read_test(void) +{ + rt_adc_device_t dev = (rt_adc_device_t)rt_device_find(ADC_TC_NAME); + + uassert_not_null(dev); + if (dev == RT_NULL) + { + return; + } + + adc_tc_reset(); + adc_tc_ctx.fail_convert = RT_FALSE; + uassert_int_equal(rt_adc_read(dev, ADC_TC_CHANNEL), ADC_TC_RAW_VALUE); + uassert_int_equal(adc_tc_ctx.convert_count, 1); + + /* a failing convert() must be reported as 0, not as a stale value */ + adc_tc_reset(); + adc_tc_ctx.fail_convert = RT_TRUE; + uassert_int_equal(rt_adc_read(dev, ADC_TC_CHANNEL), 0); + uassert_int_equal(adc_tc_ctx.convert_count, 1); +} + +static void adc_tc_voltage_test(void) +{ + rt_adc_device_t dev = (rt_adc_device_t)rt_device_find(ADC_TC_NAME); + + uassert_not_null(dev); + if (dev == RT_NULL) + { + return; + } + + adc_tc_reset(); + + /* + * Hardcoded on purpose: 1000 * 3300 / ((1 << 12) - 1) = 805. Recomputing it + * from the raw value here would use the same formula as the code under test, + * so a wrong formula would still "pass". + */ + uassert_int_equal(rt_adc_voltage(dev, ADC_TC_CHANNEL), ADC_TC_EXPECTED_MV); + + /* rt_adc_voltage() enables, converts and disables exactly once */ + uassert_int_equal(adc_tc_ctx.enable_count, 1); + uassert_int_equal(adc_tc_ctx.convert_count, 1); + uassert_int_equal(adc_tc_ctx.disable_count, 1); + uassert_true(adc_tc_ctx.enabled == RT_FALSE); +} + +/* A driver may leave the optional hooks out; the framework has to report that. */ +static void adc_tc_missing_hooks_test(void) +{ + static struct rt_adc_device minimal_dev; + rt_adc_device_t dev; + + rt_memset(&minimal_dev, 0, sizeof(minimal_dev)); + + uassert_int_equal(rt_hw_adc_register(&minimal_dev, "adc_tc_min", &adc_tc_ops_minimal, + &adc_tc_ctx), + RT_EOK); + + dev = (rt_adc_device_t)rt_device_find("adc_tc_min"); + uassert_not_null(dev); + if (dev == RT_NULL) + { + return; + } + + adc_tc_reset(); + uassert_int_equal(rt_adc_enable(dev, ADC_TC_CHANNEL), -RT_ENOSYS); + uassert_int_equal(rt_adc_disable(dev, ADC_TC_CHANNEL), -RT_ENOSYS); + uassert_int_equal(adc_tc_ctx.enable_count, 0); + uassert_int_equal(adc_tc_ctx.disable_count, 0); +} + +static rt_err_t utest_tc_init(void) +{ + adc_tc_reset(); + + return RT_EOK; +} + +static rt_err_t utest_tc_cleanup(void) +{ + return RT_EOK; +} + +static void testcase(void) +{ + UTEST_UNIT_RUN(adc_tc_register_test); + UTEST_UNIT_RUN(adc_tc_enable_disable_test); + UTEST_UNIT_RUN(adc_tc_read_test); + UTEST_UNIT_RUN(adc_tc_voltage_test); + UTEST_UNIT_RUN(adc_tc_missing_hooks_test); +} + +UTEST_TC_EXPORT(testcase, "components.drivers.adc.framework", utest_tc_init, utest_tc_cleanup, 10); + +#endif /* RT_UTEST_ADC */ diff --git a/components/utilities/utest/Kconfig b/components/utilities/utest/Kconfig index 10781a43315b..16070e9b6f7a 100644 --- a/components/utilities/utest/Kconfig +++ b/components/utilities/utest/Kconfig @@ -13,6 +13,7 @@ menu "RT-Thread Utestcases" menu "Kernel Components" menu "Drivers" + rsource "../../../components/drivers/adc/utest/Kconfig" rsource "../../../components/drivers/core/utest/Kconfig" rsource "../../../components/drivers/audio/utest/Kconfig" rsource "../../../components/drivers/ipc/utest/Kconfig"