| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671 |
- // SPDX-License-Identifier: GPL-2.0
- //
- // kselftest for the ALSA PCM API
- //
- // Original author: Jaroslav Kysela <perex@perex.cz>
- // Copyright (c) 2022 Red Hat Inc.
- // This test will iterate over all cards detected in the system, exercising
- // every PCM device it can find. This may conflict with other system
- // software if there is audio activity so is best run on a system with a
- // minimal active userspace.
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdbool.h>
- #include <errno.h>
- #include <assert.h>
- #include <pthread.h>
- #include "kselftest.h"
- #include "alsa-local.h"
- typedef struct timespec timestamp_t;
- struct card_data {
- int card;
- snd_ctl_card_info_t *info;
- const char *name;
- pthread_t thread;
- struct card_data *next;
- };
- struct card_data *card_list;
- struct pcm_data {
- snd_pcm_t *handle;
- int card;
- int device;
- int subdevice;
- const char *card_name;
- snd_pcm_stream_t stream;
- snd_config_t *pcm_config;
- struct pcm_data *next;
- };
- struct pcm_data *pcm_list;
- int num_missing;
- struct pcm_data *pcm_missing;
- snd_config_t *default_pcm_config;
- /* Lock while reporting results since kselftest doesn't */
- pthread_mutex_t results_lock = PTHREAD_MUTEX_INITIALIZER;
- enum test_class {
- TEST_CLASS_DEFAULT,
- TEST_CLASS_SYSTEM,
- };
- void timestamp_now(timestamp_t *tstamp)
- {
- if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp))
- ksft_exit_fail_msg("clock_get_time\n");
- }
- long long timestamp_diff_ms(timestamp_t *tstamp)
- {
- timestamp_t now, diff;
- timestamp_now(&now);
- if (tstamp->tv_nsec > now.tv_nsec) {
- diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1;
- diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec;
- } else {
- diff.tv_sec = now.tv_sec - tstamp->tv_sec;
- diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec;
- }
- return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L);
- }
- static long device_from_id(snd_config_t *node)
- {
- const char *id;
- char *end;
- long v;
- if (snd_config_get_id(node, &id))
- ksft_exit_fail_msg("snd_config_get_id\n");
- errno = 0;
- v = strtol(id, &end, 10);
- if (errno || *end)
- return -1;
- return v;
- }
- static void missing_device(int card, int device, int subdevice, snd_pcm_stream_t stream)
- {
- struct pcm_data *pcm_data;
- for (pcm_data = pcm_list; pcm_data != NULL; pcm_data = pcm_data->next) {
- if (pcm_data->card != card)
- continue;
- if (pcm_data->device != device)
- continue;
- if (pcm_data->subdevice != subdevice)
- continue;
- if (pcm_data->stream != stream)
- continue;
- return;
- }
- pcm_data = calloc(1, sizeof(*pcm_data));
- if (!pcm_data)
- ksft_exit_fail_msg("Out of memory\n");
- pcm_data->card = card;
- pcm_data->device = device;
- pcm_data->subdevice = subdevice;
- pcm_data->stream = stream;
- pcm_data->next = pcm_missing;
- pcm_missing = pcm_data;
- num_missing++;
- }
- static void missing_devices(int card, snd_config_t *card_config)
- {
- snd_config_t *pcm_config, *node1, *node2;
- snd_config_iterator_t i1, i2, next1, next2;
- int device, subdevice;
- pcm_config = conf_get_subtree(card_config, "pcm", NULL);
- if (!pcm_config)
- return;
- snd_config_for_each(i1, next1, pcm_config) {
- node1 = snd_config_iterator_entry(i1);
- device = device_from_id(node1);
- if (device < 0)
- continue;
- if (snd_config_get_type(node1) != SND_CONFIG_TYPE_COMPOUND)
- continue;
- snd_config_for_each(i2, next2, node1) {
- node2 = snd_config_iterator_entry(i2);
- subdevice = device_from_id(node2);
- if (subdevice < 0)
- continue;
- if (conf_get_subtree(node2, "PLAYBACK", NULL))
- missing_device(card, device, subdevice, SND_PCM_STREAM_PLAYBACK);
- if (conf_get_subtree(node2, "CAPTURE", NULL))
- missing_device(card, device, subdevice, SND_PCM_STREAM_CAPTURE);
- }
- }
- }
- static void find_pcms(void)
- {
- char name[32], key[64];
- char *card_name, *card_longname;
- int card, dev, subdev, count, direction, err;
- snd_pcm_stream_t stream;
- struct pcm_data *pcm_data;
- snd_ctl_t *handle;
- snd_pcm_info_t *pcm_info;
- snd_config_t *config, *card_config, *pcm_config;
- struct card_data *card_data;
- snd_pcm_info_alloca(&pcm_info);
- card = -1;
- if (snd_card_next(&card) < 0 || card < 0)
- return;
- config = get_alsalib_config();
- while (card >= 0) {
- card_data = calloc(1, sizeof(*card_data));
- if (!card_data)
- ksft_exit_fail_msg("Out of memory\n");
- sprintf(name, "hw:%d", card);
- err = snd_ctl_open_lconf(&handle, name, 0, config);
- if (err < 0) {
- ksft_print_msg("Failed to get hctl for card %d: %s\n",
- card, snd_strerror(err));
- goto next_card;
- }
- err = snd_card_get_name(card, &card_name);
- if (err != 0)
- card_name = "Unknown";
- err = snd_card_get_longname(card, &card_longname);
- if (err != 0)
- card_longname = "Unknown";
- err = snd_ctl_card_info_malloc(&card_data->info);
- if (err != 0)
- ksft_exit_fail_msg("Failed to allocate card info: %d\n",
- err);
- err = snd_ctl_card_info(handle, card_data->info);
- if (err == 0) {
- card_data->name = snd_ctl_card_info_get_id(card_data->info);
- if (!card_data->name)
- ksft_print_msg("Failed to get card ID\n");
- } else {
- ksft_print_msg("Failed to get card info: %d\n", err);
- }
- if (!card_data->name)
- card_data->name = "Unknown";
- ksft_print_msg("Card %d/%s - %s (%s)\n", card,
- card_data->name, card_name, card_longname);
- card_config = conf_by_card(card);
- card_data->card = card;
- card_data->next = card_list;
- card_list = card_data;
- dev = -1;
- while (1) {
- if (snd_ctl_pcm_next_device(handle, &dev) < 0)
- ksft_exit_fail_msg("snd_ctl_pcm_next_device\n");
- if (dev < 0)
- break;
- for (direction = 0; direction < 2; direction++) {
- stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
- sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream));
- pcm_config = conf_get_subtree(card_config, key, NULL);
- if (conf_get_bool(card_config, key, "skip", false)) {
- ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream));
- continue;
- }
- snd_pcm_info_set_device(pcm_info, dev);
- snd_pcm_info_set_subdevice(pcm_info, 0);
- snd_pcm_info_set_stream(pcm_info, stream);
- err = snd_ctl_pcm_info(handle, pcm_info);
- if (err == -ENOENT)
- continue;
- if (err < 0)
- ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n",
- dev, 0, stream);
- ksft_print_msg("%s.0 - %s\n", card_data->name,
- snd_pcm_info_get_id(pcm_info));
- count = snd_pcm_info_get_subdevices_count(pcm_info);
- for (subdev = 0; subdev < count; subdev++) {
- sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream));
- if (conf_get_bool(card_config, key, "skip", false)) {
- ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev,
- subdev, snd_pcm_stream_name(stream));
- continue;
- }
- pcm_data = calloc(1, sizeof(*pcm_data));
- if (!pcm_data)
- ksft_exit_fail_msg("Out of memory\n");
- pcm_data->card = card;
- pcm_data->device = dev;
- pcm_data->subdevice = subdev;
- pcm_data->card_name = card_data->name;
- pcm_data->stream = stream;
- pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL);
- pcm_data->next = pcm_list;
- pcm_list = pcm_data;
- }
- }
- }
- /* check for missing devices */
- missing_devices(card, card_config);
- next_card:
- snd_ctl_close(handle);
- if (snd_card_next(&card) < 0) {
- ksft_print_msg("snd_card_next");
- break;
- }
- }
- snd_config_delete(config);
- }
- static void test_pcm_time(struct pcm_data *data, enum test_class class,
- const char *test_name, snd_config_t *pcm_cfg)
- {
- char name[64], msg[256];
- const int duration_s = 2, margin_ms = 100;
- const int duration_ms = duration_s * 1000;
- const char *cs;
- int i, err;
- snd_pcm_t *handle = NULL;
- snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED;
- snd_pcm_format_t format, old_format;
- const char *alt_formats[8];
- unsigned char *samples = NULL;
- snd_pcm_sframes_t frames;
- long long ms;
- long rate, channels, period_size, buffer_size;
- unsigned int rrate;
- snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold;
- timestamp_t tstamp;
- bool pass = false;
- snd_pcm_hw_params_t *hw_params;
- snd_pcm_sw_params_t *sw_params;
- const char *test_class_name;
- bool skip = true;
- const char *desc;
- switch (class) {
- case TEST_CLASS_DEFAULT:
- test_class_name = "default";
- break;
- case TEST_CLASS_SYSTEM:
- test_class_name = "system";
- break;
- default:
- ksft_exit_fail_msg("Unknown test class %d\n", class);
- break;
- }
- desc = conf_get_string(pcm_cfg, "description", NULL, NULL);
- if (desc)
- ksft_print_msg("%s.%s.%s.%d.%d.%s - %s\n",
- test_class_name, test_name,
- data->card_name, data->device, data->subdevice,
- snd_pcm_stream_name(data->stream),
- desc);
- snd_pcm_hw_params_alloca(&hw_params);
- snd_pcm_sw_params_alloca(&sw_params);
- cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE");
- format = snd_pcm_format_value(cs);
- if (format == SND_PCM_FORMAT_UNKNOWN)
- ksft_exit_fail_msg("Wrong format '%s'\n", cs);
- conf_get_string_array(pcm_cfg, "alt_formats", NULL,
- alt_formats, ARRAY_SIZE(alt_formats), NULL);
- rate = conf_get_long(pcm_cfg, "rate", NULL, 48000);
- channels = conf_get_long(pcm_cfg, "channels", NULL, 2);
- period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096);
- buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384);
- samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8);
- if (!samples)
- ksft_exit_fail_msg("Out of memory\n");
- snd_pcm_format_set_silence(format, samples, rate * channels);
- sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice);
- err = snd_pcm_open(&handle, name, data->stream, 0);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err));
- goto __close;
- }
- err = snd_pcm_hw_params_any(handle, hw_params);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err));
- goto __close;
- }
- err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err));
- goto __close;
- }
- err = snd_pcm_hw_params_set_access(handle, hw_params, access);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s",
- snd_pcm_access_name(access), snd_strerror(err));
- goto __close;
- }
- i = -1;
- __format:
- err = snd_pcm_hw_params_set_format(handle, hw_params, format);
- if (err < 0) {
- i++;
- if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) {
- old_format = format;
- format = snd_pcm_format_value(alt_formats[i]);
- if (format != SND_PCM_FORMAT_UNKNOWN) {
- ksft_print_msg("%s.%s.%d.%d.%s.%s format %s -> %s\n",
- test_name,
- data->card_name, data->device, data->subdevice,
- snd_pcm_stream_name(data->stream),
- snd_pcm_access_name(access),
- snd_pcm_format_name(old_format),
- snd_pcm_format_name(format));
- samples = realloc(samples, (rate * channels *
- snd_pcm_format_physical_width(format)) / 8);
- if (!samples)
- ksft_exit_fail_msg("Out of memory\n");
- snd_pcm_format_set_silence(format, samples, rate * channels);
- goto __format;
- }
- }
- snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s",
- snd_pcm_format_name(format), snd_strerror(err));
- goto __close;
- }
- err = snd_pcm_hw_params_set_channels(handle, hw_params, channels);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err));
- goto __close;
- }
- rrate = rate;
- err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err));
- goto __close;
- }
- if (rrate != rate) {
- snprintf(msg, sizeof(msg), "rate mismatch %ld != %u", rate, rrate);
- goto __close;
- }
- rperiod_size = period_size;
- err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err));
- goto __close;
- }
- rbuffer_size = buffer_size;
- err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err));
- goto __close;
- }
- err = snd_pcm_hw_params(handle, hw_params);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err));
- goto __close;
- }
- err = snd_pcm_sw_params_current(handle, sw_params);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err));
- goto __close;
- }
- if (data->stream == SND_PCM_STREAM_PLAYBACK) {
- start_threshold = (rbuffer_size / rperiod_size) * rperiod_size;
- } else {
- start_threshold = rperiod_size;
- }
- err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err));
- goto __close;
- }
- err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err));
- goto __close;
- }
- err = snd_pcm_sw_params(handle, sw_params);
- if (err < 0) {
- snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err));
- goto __close;
- }
- ksft_print_msg("%s.%s.%s.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n",
- test_class_name, test_name,
- data->card_name, data->device, data->subdevice,
- snd_pcm_stream_name(data->stream),
- snd_pcm_access_name(access),
- snd_pcm_format_name(format),
- (long)rate, (long)channels,
- (long)rperiod_size, (long)rbuffer_size,
- (long)start_threshold);
- /* Set all the params, actually run the test */
- skip = false;
- timestamp_now(&tstamp);
- for (i = 0; i < duration_s; i++) {
- if (data->stream == SND_PCM_STREAM_PLAYBACK) {
- frames = snd_pcm_writei(handle, samples, rate);
- if (frames < 0) {
- snprintf(msg, sizeof(msg),
- "Write failed: expected %ld, wrote %li", rate, frames);
- goto __close;
- }
- if (frames < rate) {
- snprintf(msg, sizeof(msg),
- "expected %ld, wrote %li", rate, frames);
- goto __close;
- }
- } else {
- frames = snd_pcm_readi(handle, samples, rate);
- if (frames < 0) {
- snprintf(msg, sizeof(msg),
- "expected %ld, wrote %li", rate, frames);
- goto __close;
- }
- if (frames < rate) {
- snprintf(msg, sizeof(msg),
- "expected %ld, wrote %li", rate, frames);
- goto __close;
- }
- }
- }
- snd_pcm_drain(handle);
- ms = timestamp_diff_ms(&tstamp);
- if (ms < duration_ms - margin_ms || ms > duration_ms + margin_ms) {
- snprintf(msg, sizeof(msg), "time mismatch: expected %dms got %lld", duration_ms, ms);
- goto __close;
- }
- msg[0] = '\0';
- pass = true;
- __close:
- pthread_mutex_lock(&results_lock);
- switch (class) {
- case TEST_CLASS_SYSTEM:
- test_class_name = "system";
- /*
- * Anything specified as specific to this system
- * should always be supported.
- */
- ksft_test_result(!skip, "%s.%s.%s.%d.%d.%s.params\n",
- test_class_name, test_name,
- data->card_name, data->device,
- data->subdevice,
- snd_pcm_stream_name(data->stream));
- break;
- default:
- break;
- }
- if (!skip)
- ksft_test_result(pass, "%s.%s.%s.%d.%d.%s\n",
- test_class_name, test_name,
- data->card_name, data->device,
- data->subdevice,
- snd_pcm_stream_name(data->stream));
- else
- ksft_test_result_skip("%s.%s.%s.%d.%d.%s\n",
- test_class_name, test_name,
- data->card_name, data->device,
- data->subdevice,
- snd_pcm_stream_name(data->stream));
- if (msg[0])
- ksft_print_msg("%s\n", msg);
- pthread_mutex_unlock(&results_lock);
- free(samples);
- if (handle)
- snd_pcm_close(handle);
- }
- void run_time_tests(struct pcm_data *pcm, enum test_class class,
- snd_config_t *cfg)
- {
- const char *test_name, *test_type;
- snd_config_t *pcm_cfg;
- snd_config_iterator_t i, next;
- if (!cfg)
- return;
- cfg = conf_get_subtree(cfg, "test", NULL);
- if (cfg == NULL)
- return;
- snd_config_for_each(i, next, cfg) {
- pcm_cfg = snd_config_iterator_entry(i);
- if (snd_config_get_id(pcm_cfg, &test_name) < 0)
- ksft_exit_fail_msg("snd_config_get_id\n");
- test_type = conf_get_string(pcm_cfg, "type", NULL, "time");
- if (strcmp(test_type, "time") == 0)
- test_pcm_time(pcm, class, test_name, pcm_cfg);
- else
- ksft_exit_fail_msg("unknown test type '%s'\n", test_type);
- }
- }
- void *card_thread(void *data)
- {
- struct card_data *card = data;
- struct pcm_data *pcm;
- for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
- if (pcm->card != card->card)
- continue;
- run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config);
- run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config);
- }
- return 0;
- }
- int main(void)
- {
- struct card_data *card;
- struct card_cfg_data *conf;
- struct pcm_data *pcm;
- snd_config_t *global_config, *cfg;
- int num_pcm_tests = 0, num_tests, num_std_pcm_tests;
- int ret;
- void *thread_ret;
- ksft_print_header();
- global_config = conf_load_from_file("pcm-test.conf");
- default_pcm_config = conf_get_subtree(global_config, "pcm", NULL);
- if (default_pcm_config == NULL)
- ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n");
- conf_load();
- find_pcms();
- for (conf = conf_cards; conf; conf = conf->next)
- if (conf->card < 0)
- num_missing++;
- num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL);
- for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
- num_pcm_tests += num_std_pcm_tests;
- cfg = pcm->pcm_config;
- if (cfg == NULL)
- continue;
- /* Setting params is reported as a separate test */
- num_tests = conf_get_count(cfg, "test", NULL) * 2;
- if (num_tests > 0)
- num_pcm_tests += num_tests;
- }
- ksft_set_plan(num_missing + num_pcm_tests);
- for (conf = conf_cards; conf; conf = conf->next)
- if (conf->card < 0)
- ksft_test_result_fail("test.missing.%s.%s\n",
- conf->filename, conf->config_id);
- for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) {
- ksft_test_result(false, "test.missing.%s.%d.%d.%s\n",
- pcm->card_name, pcm->device, pcm->subdevice,
- snd_pcm_stream_name(pcm->stream));
- }
- for (card = card_list; card != NULL; card = card->next) {
- ret = pthread_create(&card->thread, NULL, card_thread, card);
- if (ret != 0) {
- ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n",
- card->card, ret,
- strerror(errno));
- }
- }
- for (card = card_list; card != NULL; card = card->next) {
- ret = pthread_join(card->thread, &thread_ret);
- if (ret != 0) {
- ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n",
- card->card, ret,
- strerror(errno));
- }
- }
- snd_config_delete(global_config);
- conf_free();
- ksft_exit_pass();
- return 0;
- }
|