ALSA: core: Add sound core KUnit test
authorIvan Orlov <ivan.orlov0322@gmail.com>
Thu, 25 Jan 2024 22:35:21 +0000 (22:35 +0000)
committerTakashi Iwai <tiwai@suse.de>
Tue, 30 Jan 2024 13:11:37 +0000 (14:11 +0100)
At the moment, we have a decent amount of integration tests (selftests)
covering different aspects of the sound subsystem. However, a lot of
of sound-related in-kernel functions remains uncovered. This patch
introduces the KUnit test for the core part of the sound subsystem.
It includes 10 test cases:

- Coverage of the format-related inline functions from 'pcm.h' header
file: snd_pcm_format_physical_width, snd_pcm_format_width,
snd_pcm_format_signed, test_format_endianness

- Coverage of the available bytes counting functions from 'pcm.h'
header: snd_pcm_capture_avail, snd_pcm_playback_avail

- Coverage of functions from pcm_misc: snd_pcm_format_set_silence,
snd_pcm_format_name

- Coverage of card-related functions from init.c: snd_card_set_id,
snd_component_add

This patch depends on the previous patches in this patch series as they
contain fix for the bug, which was found during the test development.
Without them, the test doesn't pass.

Signed-off-by: Ivan Orlov <ivan.orlov0322@gmail.com>
Link: https://lore.kernel.org/r/20240125223522.1122765-3-ivan.orlov0322@gmail.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
MAINTAINERS
sound/core/Kconfig
sound/core/Makefile
sound/core/sound_kunit.c [new file with mode: 0644]

index 8d1052fa6a6924d17a4d2681fa7907c544e35186..dbc61d5657600576602e16278caf3cbb84795edc 100644 (file)
@@ -20484,6 +20484,12 @@ F:     include/uapi/sound/compress_*
 F:     sound/core/compress_offload.c
 F:     sound/soc/soc-compress.c
 
+SOUND - CORE KUNIT TEST
+M:     Ivan Orlov <ivan.orlov0322@gmail.com>
+L:     linux-sound@vger.kernel.org
+S:     Supported
+F:     sound/core/sound_kunit.c
+
 SOUND - DMAENGINE HELPERS
 M:     Lars-Peter Clausen <lars@metafoo.de>
 S:     Supported
index e41818e59a1585e1bd164e094118e0e9d5ffce2c..664c6ee2b5a1de9d73e11e8aa9a15365a28c20e8 100644 (file)
@@ -39,6 +39,22 @@ config SND_UMP_LEGACY_RAWMIDI
          legacy MIDI 1.0 byte streams is created for each UMP Endpoint.
          The device contains 16 substreams corresponding to UMP groups.
 
+config SND_CORE_TEST
+       tristate "Sound core KUnit test"
+       depends on KUNIT
+       default KUNIT_ALL_TESTS
+       help
+         This options enables the sound core functions KUnit test.
+
+         KUnit tests run during boot and output the results to the debug
+         log in TAP format (https://testanything.org/). Only useful for
+         kernel devs running KUnit test harness and are not for inclusion
+         into a production build.
+
+         For more information on KUnit and unit tests in general, refer
+         to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+
 config SND_COMPRESS_OFFLOAD
        tristate
 
index a6b444ee283264ca60e8d5673c4378f6175f128c..1d34e6950317c01a4ec579e8475391698d71c3dc 100644 (file)
@@ -49,6 +49,8 @@ obj-$(CONFIG_SND_SEQ_DEVICE)  += snd-seq-device.o
 obj-$(CONFIG_SND_RAWMIDI)      += snd-rawmidi.o
 obj-$(CONFIG_SND_UMP)          += snd-ump.o
 
+obj-$(CONFIG_SND_CORE_TEST)    += sound_kunit.o
+
 obj-$(CONFIG_SND_OSSEMUL)      += oss/
 obj-$(CONFIG_SND_SEQUENCER)    += seq/
 
diff --git a/sound/core/sound_kunit.c b/sound/core/sound_kunit.c
new file mode 100644 (file)
index 0000000..5d5a7bf
--- /dev/null
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Sound core KUnit test
+ * Author: Ivan Orlov <ivan.orlov0322@gmail.com>
+ */
+
+#include <kunit/test.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#define SILENCE_BUFFER_SIZE 2048
+#define SILENCE(...) { __VA_ARGS__ }
+#define DEFINE_FORMAT(fmt, pbits, wd, endianness, signd, silence_arr) {                \
+       .format = SNDRV_PCM_FORMAT_##fmt, .physical_bits = pbits,               \
+       .width = wd, .le = endianness, .sd = signd, .silence = silence_arr,     \
+       .name = #fmt,                                                           \
+}
+
+#define WRONG_FORMAT (SNDRV_PCM_FORMAT_LAST + 1)
+
+#define VALID_NAME "ValidName"
+#define NAME_W_SPEC_CHARS "In%v@1id name"
+#define NAME_W_SPACE "Test name"
+#define NAME_W_SPACE_REMOVED "Testname"
+
+#define TEST_FIRST_COMPONENT "Component1"
+#define TEST_SECOND_COMPONENT "Component2"
+
+struct snd_format_test_data {
+       snd_pcm_format_t format;
+       int physical_bits;
+       int width;
+       int le;
+       int sd;
+       unsigned char silence[8];
+       unsigned char *name;
+};
+
+struct avail_test_data {
+       snd_pcm_uframes_t buffer_size;
+       snd_pcm_uframes_t hw_ptr;
+       snd_pcm_uframes_t appl_ptr;
+       snd_pcm_uframes_t expected_avail;
+};
+
+static struct snd_format_test_data valid_fmt[] = {
+       DEFINE_FORMAT(S8, 8, 8, -1, 1, SILENCE()),
+       DEFINE_FORMAT(U8, 8, 8, -1, 0, SILENCE(0x80)),
+       DEFINE_FORMAT(S16_LE, 16, 16, 1, 1, SILENCE()),
+       DEFINE_FORMAT(S16_BE, 16, 16, 0, 1, SILENCE()),
+       DEFINE_FORMAT(U16_LE, 16, 16, 1, 0, SILENCE(0x00, 0x80)),
+       DEFINE_FORMAT(U16_BE, 16, 16, 0, 0, SILENCE(0x80, 0x00)),
+       DEFINE_FORMAT(S24_LE, 32, 24, 1, 1, SILENCE()),
+       DEFINE_FORMAT(S24_BE, 32, 24, 0, 1, SILENCE()),
+       DEFINE_FORMAT(U24_LE, 32, 24, 1, 0, SILENCE(0x00, 0x00, 0x80)),
+       DEFINE_FORMAT(U24_BE, 32, 24, 0, 0, SILENCE(0x00, 0x80, 0x00, 0x00)),
+       DEFINE_FORMAT(S32_LE, 32, 32, 1, 1, SILENCE()),
+       DEFINE_FORMAT(S32_BE, 32, 32, 0, 1, SILENCE()),
+       DEFINE_FORMAT(U32_LE, 32, 32, 1, 0, SILENCE(0x00, 0x00, 0x00, 0x80)),
+       DEFINE_FORMAT(U32_BE, 32, 32, 0, 0, SILENCE(0x80, 0x00, 0x00, 0x00)),
+       DEFINE_FORMAT(FLOAT_LE, 32, 32, 1, -1, SILENCE()),
+       DEFINE_FORMAT(FLOAT_BE, 32, 32, 0, -1, SILENCE()),
+       DEFINE_FORMAT(FLOAT64_LE, 64, 64, 1, -1, SILENCE()),
+       DEFINE_FORMAT(FLOAT64_BE, 64, 64, 0, -1, SILENCE()),
+       DEFINE_FORMAT(IEC958_SUBFRAME_LE, 32, 32, 1, -1, SILENCE()),
+       DEFINE_FORMAT(IEC958_SUBFRAME_BE, 32, 32, 0, -1, SILENCE()),
+       DEFINE_FORMAT(MU_LAW, 8, 8, -1, -1, SILENCE(0x7f)),
+       DEFINE_FORMAT(A_LAW, 8, 8, -1, -1, SILENCE(0x55)),
+       DEFINE_FORMAT(IMA_ADPCM, 4, 4, -1, -1, SILENCE()),
+       DEFINE_FORMAT(G723_24, 3, 3, -1, -1, SILENCE()),
+       DEFINE_FORMAT(G723_40, 5, 5, -1, -1, SILENCE()),
+       DEFINE_FORMAT(DSD_U8, 8, 8, 1, 0, SILENCE(0x69)),
+       DEFINE_FORMAT(DSD_U16_LE, 16, 16, 1, 0, SILENCE(0x69, 0x69)),
+       DEFINE_FORMAT(DSD_U32_LE, 32, 32, 1, 0, SILENCE(0x69, 0x69, 0x69, 0x69)),
+       DEFINE_FORMAT(DSD_U16_BE, 16, 16, 0, 0, SILENCE(0x69, 0x69)),
+       DEFINE_FORMAT(DSD_U32_BE, 32, 32, 0, 0, SILENCE(0x69, 0x69, 0x69, 0x69)),
+       DEFINE_FORMAT(S20_LE, 32, 20, 1, 1, SILENCE()),
+       DEFINE_FORMAT(S20_BE, 32, 20, 0, 1, SILENCE()),
+       DEFINE_FORMAT(U20_LE, 32, 20, 1, 0, SILENCE(0x00, 0x00, 0x08, 0x00)),
+       DEFINE_FORMAT(U20_BE, 32, 20, 0, 0, SILENCE(0x00, 0x08, 0x00, 0x00)),
+       DEFINE_FORMAT(S24_3LE, 24, 24, 1, 1, SILENCE()),
+       DEFINE_FORMAT(S24_3BE, 24, 24, 0, 1, SILENCE()),
+       DEFINE_FORMAT(U24_3LE, 24, 24, 1, 0, SILENCE(0x00, 0x00, 0x80)),
+       DEFINE_FORMAT(U24_3BE, 24, 24, 0, 0, SILENCE(0x80, 0x00, 0x00)),
+       DEFINE_FORMAT(S20_3LE, 24, 20, 1, 1, SILENCE()),
+       DEFINE_FORMAT(S20_3BE, 24, 20, 0, 1, SILENCE()),
+       DEFINE_FORMAT(U20_3LE, 24, 20, 1, 0, SILENCE(0x00, 0x00, 0x08)),
+       DEFINE_FORMAT(U20_3BE, 24, 20, 0, 0, SILENCE(0x08, 0x00, 0x00)),
+       DEFINE_FORMAT(S18_3LE, 24, 18, 1, 1, SILENCE()),
+       DEFINE_FORMAT(S18_3BE, 24, 18, 0, 1, SILENCE()),
+       DEFINE_FORMAT(U18_3LE, 24, 18, 1, 0, SILENCE(0x00, 0x00, 0x02)),
+       DEFINE_FORMAT(U18_3BE, 24, 18, 0, 0, SILENCE(0x02, 0x00, 0x00)),
+       DEFINE_FORMAT(G723_24_1B, 8, 3, -1, -1, SILENCE()),
+       DEFINE_FORMAT(G723_40_1B, 8, 5, -1, -1, SILENCE()),
+};
+
+static void test_phys_format_size(struct kunit *test)
+{
+       u32 i;
+
+       for (i = 0; i < ARRAY_SIZE(valid_fmt); i++) {
+               KUNIT_EXPECT_EQ(test, snd_pcm_format_physical_width(valid_fmt[i].format),
+                               valid_fmt[i].physical_bits);
+       }
+
+       KUNIT_EXPECT_EQ(test, snd_pcm_format_physical_width(WRONG_FORMAT), -EINVAL);
+       KUNIT_EXPECT_EQ(test, snd_pcm_format_physical_width(-1), -EINVAL);
+}
+
+static void test_format_width(struct kunit *test)
+{
+       u32 i;
+
+       for (i = 0; i < ARRAY_SIZE(valid_fmt); i++) {
+               KUNIT_EXPECT_EQ(test, snd_pcm_format_width(valid_fmt[i].format),
+                               valid_fmt[i].width);
+       }
+
+       KUNIT_EXPECT_EQ(test, snd_pcm_format_width(WRONG_FORMAT), -EINVAL);
+       KUNIT_EXPECT_EQ(test, snd_pcm_format_width(-1), -EINVAL);
+}
+
+static void test_format_signed(struct kunit *test)
+{
+       u32 i;
+
+       for (i = 0; i < ARRAY_SIZE(valid_fmt); i++) {
+               KUNIT_EXPECT_EQ(test, snd_pcm_format_signed(valid_fmt[i].format),
+                               valid_fmt[i].sd < 0 ? -EINVAL : valid_fmt[i].sd);
+               KUNIT_EXPECT_EQ(test, snd_pcm_format_unsigned(valid_fmt[i].format),
+                               valid_fmt[i].sd < 0 ? -EINVAL : 1 - valid_fmt[i].sd);
+       }
+
+       KUNIT_EXPECT_EQ(test, snd_pcm_format_width(WRONG_FORMAT), -EINVAL);
+       KUNIT_EXPECT_EQ(test, snd_pcm_format_width(-1), -EINVAL);
+}
+
+static void test_format_endianness(struct kunit *test)
+{
+       u32 i;
+
+       for (i = 0; i < ARRAY_SIZE(valid_fmt); i++) {
+               KUNIT_EXPECT_EQ(test, snd_pcm_format_little_endian(valid_fmt[i].format),
+                               valid_fmt[i].le < 0 ? -EINVAL : valid_fmt[i].le);
+               KUNIT_EXPECT_EQ(test, snd_pcm_format_big_endian(valid_fmt[i].format),
+                               valid_fmt[i].le < 0 ? -EINVAL : 1 - valid_fmt[i].le);
+       }
+
+       KUNIT_EXPECT_EQ(test, snd_pcm_format_little_endian(WRONG_FORMAT), -EINVAL);
+       KUNIT_EXPECT_EQ(test, snd_pcm_format_little_endian(-1), -EINVAL);
+       KUNIT_EXPECT_EQ(test, snd_pcm_format_big_endian(WRONG_FORMAT), -EINVAL);
+       KUNIT_EXPECT_EQ(test, snd_pcm_format_big_endian(-1), -EINVAL);
+}
+
+static void _test_fill_silence(struct kunit *test, struct snd_format_test_data *data,
+                              u8 *buffer, size_t samples_count)
+{
+       size_t sample_bytes = data->physical_bits >> 3;
+       u32 i;
+
+       KUNIT_ASSERT_EQ(test, snd_pcm_format_set_silence(data->format, buffer, samples_count), 0);
+       for (i = 0; i < samples_count * sample_bytes; i++)
+               KUNIT_EXPECT_EQ(test, buffer[i], data->silence[i % sample_bytes]);
+}
+
+static void test_format_fill_silence(struct kunit *test)
+{
+       u32 buf_samples[] = { 10, 20, 32, 64, 129, 260 };
+       u8 *buffer;
+       u32 i, j;
+
+       buffer = kunit_kzalloc(test, SILENCE_BUFFER_SIZE, GFP_KERNEL);
+
+       for (i = 0; i < ARRAY_SIZE(buf_samples); i++) {
+               for (j = 0; j < ARRAY_SIZE(valid_fmt); j++)
+                       _test_fill_silence(test, &valid_fmt[j], buffer, buf_samples[i]);
+       }
+
+       KUNIT_EXPECT_EQ(test, snd_pcm_format_set_silence(WRONG_FORMAT, buffer, 20), -EINVAL);
+       KUNIT_EXPECT_EQ(test, snd_pcm_format_set_silence(SNDRV_PCM_FORMAT_LAST, buffer, 0), 0);
+}
+
+static snd_pcm_uframes_t calculate_boundary(snd_pcm_uframes_t buffer_size)
+{
+       snd_pcm_uframes_t boundary = buffer_size;
+
+       while (boundary * 2 <= 0x7fffffffUL - buffer_size)
+               boundary *= 2;
+       return boundary;
+}
+
+static struct avail_test_data p_avail_data[] = {
+       /* buf_size + hw_ptr < appl_ptr => avail = buf_size + hw_ptr - appl_ptr + boundary */
+       { 128, 1000, 1129, 1073741824UL - 1 },
+       /*
+        * buf_size + hw_ptr - appl_ptr >= boundary =>
+        * => avail = buf_size + hw_ptr - appl_ptr - boundary
+        */
+       { 128, 1073741824UL, 10, 118 },
+       /* standard case: avail = buf_size + hw_ptr - appl_ptr */
+       { 128, 1000, 1001, 127 },
+};
+
+static void test_playback_avail(struct kunit *test)
+{
+       struct snd_pcm_runtime *r = kunit_kzalloc(test, sizeof(*r), GFP_KERNEL);
+       u32 i;
+
+       r->status = kunit_kzalloc(test, sizeof(*r->status), GFP_KERNEL);
+       r->control = kunit_kzalloc(test, sizeof(*r->control), GFP_KERNEL);
+
+       for (i = 0; i < ARRAY_SIZE(p_avail_data); i++) {
+               r->buffer_size = p_avail_data[i].buffer_size;
+               r->boundary = calculate_boundary(r->buffer_size);
+               r->status->hw_ptr = p_avail_data[i].hw_ptr;
+               r->control->appl_ptr = p_avail_data[i].appl_ptr;
+               KUNIT_EXPECT_EQ(test, snd_pcm_playback_avail(r), p_avail_data[i].expected_avail);
+       }
+}
+
+static struct avail_test_data c_avail_data[] = {
+       /* hw_ptr - appl_ptr < 0 => avail = hw_ptr - appl_ptr + boundary */
+       { 128, 1000, 1001, 1073741824UL - 1 },
+       /* standard case: avail = hw_ptr - appl_ptr */
+       { 128, 1001, 1000, 1 },
+};
+
+static void test_capture_avail(struct kunit *test)
+{
+       struct snd_pcm_runtime *r = kunit_kzalloc(test, sizeof(*r), GFP_KERNEL);
+       u32 i;
+
+       r->status = kunit_kzalloc(test, sizeof(*r->status), GFP_KERNEL);
+       r->control = kunit_kzalloc(test, sizeof(*r->control), GFP_KERNEL);
+
+       for (i = 0; i < ARRAY_SIZE(c_avail_data); i++) {
+               r->buffer_size = c_avail_data[i].buffer_size;
+               r->boundary = calculate_boundary(r->buffer_size);
+               r->status->hw_ptr = c_avail_data[i].hw_ptr;
+               r->control->appl_ptr = c_avail_data[i].appl_ptr;
+               KUNIT_EXPECT_EQ(test, snd_pcm_capture_avail(r), c_avail_data[i].expected_avail);
+       }
+}
+
+static void test_card_set_id(struct kunit *test)
+{
+       struct snd_card *card = kunit_kzalloc(test, sizeof(*card), GFP_KERNEL);
+
+       snd_card_set_id(card, VALID_NAME);
+       KUNIT_EXPECT_STREQ(test, card->id, VALID_NAME);
+
+       /* clear the first id character so we can set it again */
+       card->id[0] = '\0';
+       snd_card_set_id(card, NAME_W_SPEC_CHARS);
+       KUNIT_EXPECT_STRNEQ(test, card->id, NAME_W_SPEC_CHARS);
+
+       card->id[0] = '\0';
+       snd_card_set_id(card, NAME_W_SPACE);
+       kunit_info(test, "%s", card->id);
+       KUNIT_EXPECT_STREQ(test, card->id, NAME_W_SPACE_REMOVED);
+}
+
+static void test_pcm_format_name(struct kunit *test)
+{
+       u32 i;
+       const char *name;
+
+       for (i = 0; i < ARRAY_SIZE(valid_fmt); i++) {
+               name = snd_pcm_format_name(valid_fmt[i].format);
+               KUNIT_ASSERT_NOT_NULL_MSG(test, name, "Don't have name for %s", valid_fmt[i].name);
+               KUNIT_EXPECT_STREQ(test, name, valid_fmt[i].name);
+       }
+
+       KUNIT_ASSERT_STREQ(test, snd_pcm_format_name(WRONG_FORMAT), "Unknown");
+       KUNIT_ASSERT_STREQ(test, snd_pcm_format_name(-1), "Unknown");
+}
+
+static void test_card_add_component(struct kunit *test)
+{
+       struct snd_card *card = kunit_kzalloc(test, sizeof(*card), GFP_KERNEL);
+
+       snd_component_add(card, TEST_FIRST_COMPONENT);
+       KUNIT_ASSERT_STREQ(test, card->components, TEST_FIRST_COMPONENT);
+
+       snd_component_add(card, TEST_SECOND_COMPONENT);
+       KUNIT_ASSERT_STREQ(test, card->components, TEST_FIRST_COMPONENT " " TEST_SECOND_COMPONENT);
+}
+
+static struct kunit_case sound_utils_cases[] = {
+       KUNIT_CASE(test_phys_format_size),
+       KUNIT_CASE(test_format_width),
+       KUNIT_CASE(test_format_endianness),
+       KUNIT_CASE(test_format_signed),
+       KUNIT_CASE(test_format_fill_silence),
+       KUNIT_CASE(test_playback_avail),
+       KUNIT_CASE(test_capture_avail),
+       KUNIT_CASE(test_card_set_id),
+       KUNIT_CASE(test_pcm_format_name),
+       KUNIT_CASE(test_card_add_component),
+       {},
+};
+
+static struct kunit_suite sound_utils_suite = {
+       .name = "sound-core-test",
+       .test_cases = sound_utils_cases,
+};
+
+kunit_test_suite(sound_utils_suite);
+MODULE_AUTHOR("Ivan Orlov");
+MODULE_LICENSE("GPL");