testing/libc/popen: add popen/dpopen test for shell and no-shell modes

Add tests for popen()/pclose() and dpopen()/dpclose() that work in
both shell (NSH) and no-shell (direct posix_spawnp) modes.  The test
binary doubles as its own target via the --echo sub-command.

Tests:
 1-4: popen  - basic read, multi-arg, pclose, invalid mode
 5-7: dpopen - basic read, multi-arg, dpclose

Signed-off-by: cuiziwei <cuiziwei@xiaomi.com>
This commit is contained in:
cuiziwei 2026-04-28 17:29:12 +08:00 committed by Xiang Xiao
parent 740fda9630
commit 97802bd9e0
5 changed files with 356 additions and 0 deletions

View file

@ -0,0 +1,33 @@
# ##############################################################################
# apps/testing/libc/popen/CMakeLists.txt
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more contributor
# license agreements. See the NOTICE file distributed with this work for
# additional information regarding copyright ownership. The ASF licenses this
# file to you under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
#
# ##############################################################################
if(CONFIG_TESTING_POPEN)
nuttx_add_application(
NAME
popen_test
STACKSIZE
${CONFIG_DEFAULT_TASK_STACKSIZE}
MODULE
${CONFIG_TESTING_POPEN}
SRCS
popen_test.c)
endif()

View file

@ -0,0 +1,16 @@
#
# For a description of the syntax of this configuration file,
# see the file kconfig-language.txt in the NuttX tools repository.
#
config TESTING_POPEN
tristate "popen()/dpopen() test"
default n
depends on SYSTEM_POPEN
---help---
Test popen()/pclose() and dpopen()/dpclose() in both shell
and no-shell modes. The test binary doubles as its own
popen/dpopen target via --echo.
if TESTING_POPEN
endif

View file

@ -0,0 +1,25 @@
############################################################################
# apps/testing/libc/popen/Make.defs
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
ifneq ($(CONFIG_TESTING_POPEN),)
CONFIGURED_APPS += $(APPDIR)/testing/libc/popen
endif

View file

@ -0,0 +1,32 @@
############################################################################
# apps/testing/libc/popen/Makefile
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
include $(APPDIR)/Make.defs
PROGNAME = popen_test
PRIORITY = SCHED_PRIORITY_DEFAULT
STACKSIZE = $(CONFIG_DEFAULT_TASK_STACKSIZE)
MODULE = $(CONFIG_TESTING_POPEN)
MAINSRC = popen_test.c
include $(APPDIR)/Application.mk

View file

@ -0,0 +1,250 @@
/****************************************************************************
* apps/testing/libc/popen/popen_test.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
/****************************************************************************
* Private Functions
****************************************************************************/
static int test_popen_read(void)
{
FILE *fp;
char buf[256];
int found = 0;
printf("[TEST 1] popen read...\n");
fp = popen("popen_test --echo hello_popen", "r");
if (fp == NULL)
{
printf("[FAIL] popen returned NULL\n");
return 1;
}
while (fgets(buf, sizeof(buf), fp) != NULL)
{
printf(" > %s", buf);
if (strstr(buf, "hello_popen"))
{
found = 1;
}
}
pclose(fp);
printf("%s\n", found ? "[PASS]" : "[FAIL] missing output");
return !found;
}
static int test_popen_multiarg(void)
{
FILE *fp;
char buf[256];
int found = 0;
printf("[TEST 2] popen multi-arg...\n");
fp = popen("popen_test --echo aaa bbb ccc", "r");
if (fp == NULL)
{
printf("[FAIL] popen returned NULL\n");
return 1;
}
while (fgets(buf, sizeof(buf), fp) != NULL)
{
printf(" > %s", buf);
if (strstr(buf, "aaa bbb ccc"))
{
found = 1;
}
}
pclose(fp);
printf("%s\n", found ? "[PASS]" : "[FAIL] wrong output");
return !found;
}
static int test_pclose_complete(void)
{
FILE *fp;
char buf[256];
printf("[TEST 3] pclose completes...\n");
fp = popen("popen_test --echo ok", "r");
if (fp == NULL)
{
printf("[FAIL] popen returned NULL\n");
return 1;
}
while (fgets(buf, sizeof(buf), fp) != NULL);
pclose(fp);
printf("[PASS]\n");
return 0;
}
static int test_popen_invalid_mode(void)
{
FILE *fp;
printf("[TEST 4] popen invalid mode...\n");
fp = popen("popen_test", "x");
if (fp == NULL)
{
printf("[PASS]\n");
return 0;
}
pclose(fp);
printf("[FAIL] should return NULL\n");
return 1;
}
static int test_dpopen_read(void)
{
char buf[256];
ssize_t n;
pid_t pid;
int found = 0;
int fd;
printf("[TEST 5] dpopen read...\n");
fd = dpopen("popen_test --echo hello_dpopen", O_RDONLY, &pid);
if (fd < 0)
{
printf("[FAIL] dpopen returned %d\n", fd);
return 1;
}
while ((n = read(fd, buf, sizeof(buf) - 1)) > 0)
{
buf[n] = '\0';
printf(" > %s", buf);
if (strstr(buf, "hello_dpopen"))
{
found = 1;
}
}
dpclose(fd, pid);
printf("%s\n", found ? "[PASS]" : "[FAIL] missing output");
return !found;
}
static int test_dpopen_multiarg(void)
{
char buf[256];
ssize_t n;
pid_t pid;
int found = 0;
int fd;
printf("[TEST 6] dpopen multi-arg...\n");
fd = dpopen("popen_test --echo xxx yyy zzz", O_RDONLY, &pid);
if (fd < 0)
{
printf("[FAIL] dpopen returned %d\n", fd);
return 1;
}
while ((n = read(fd, buf, sizeof(buf) - 1)) > 0)
{
buf[n] = '\0';
printf(" > %s", buf);
if (strstr(buf, "xxx yyy zzz"))
{
found = 1;
}
}
dpclose(fd, pid);
printf("%s\n", found ? "[PASS]" : "[FAIL] wrong output");
return !found;
}
static int test_dpclose_complete(void)
{
char buf[256];
pid_t pid;
int fd;
printf("[TEST 7] dpclose completes...\n");
fd = dpopen("popen_test --echo ok", O_RDONLY, &pid);
if (fd < 0)
{
printf("[FAIL] dpopen returned %d\n", fd);
return 1;
}
while (read(fd, buf, sizeof(buf)) > 0);
dpclose(fd, pid);
printf("[PASS]\n");
return 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, FAR char *argv[])
{
int fail = 0;
/* Sub-command: echo args back, used as popen/dpopen target */
if (argc > 1 && strcmp(argv[1], "--echo") == 0)
{
int i;
for (i = 2; i < argc; i++)
{
printf("%s%s", i > 2 ? " " : "", argv[i]);
}
printf("\n");
return 0;
}
printf("=== popen/dpopen test ===\n\n");
/* popen tests */
fail += test_popen_read();
fail += test_popen_multiarg();
fail += test_pclose_complete();
fail += test_popen_invalid_mode();
/* dpopen tests */
fail += test_dpopen_read();
fail += test_dpopen_multiarg();
fail += test_dpclose_complete();
printf("\n=== Results: %d failed ===\n", fail);
return fail;
}