From 96a3adad2cb84b30c4bcebd86bc83ba9ea79e241 Mon Sep 17 00:00:00 2001 From: kevinzcheng Date: Fri, 13 Mar 2026 17:12:07 +0800 Subject: [PATCH] Initializing the repository --- LICENSE | 4 +- README.en.md | 209 +++++- README.md | 208 +++++- baseline/cases | 225 +++++++ baseline/cases_app | 4 + baseline/cases_baseos | 108 +++ baseline/cases_develop | 38 ++ baseline/cases_driver | 4 + baseline/cases_kernel | 66 ++ control | 14 + os_smoke_src/Makefile | 22 + os_smoke_src/lib/Makefile | 11 + os_smoke_src/lib/common_libs.sh | 119 ++++ os_smoke_src/testcases/Makefile | 22 + os_smoke_src/testcases/app/Makefile | 22 + os_smoke_src/testcases/baseos/Makefile | 22 + .../testcases/baseos/command/Makefile | 36 + .../testcases/baseos/command/awk_tc001.sh | 84 +++ .../testcases/baseos/command/bzip2_tc001.sh | 78 +++ .../testcases/baseos/command/cat_tc001.sh | 73 ++ .../testcases/baseos/command/chmod_tc001.sh | 82 +++ .../testcases/baseos/command/chown_tc001.sh | 93 +++ .../testcases/baseos/command/cp_tc001.sh | 89 +++ .../testcases/baseos/command/cut_tc001.sh | 84 +++ .../testcases/baseos/command/dd_tc001.sh | 84 +++ .../testcases/baseos/command/df_tc001.sh | 88 +++ .../testcases/baseos/command/du_tc001.sh | 75 +++ .../testcases/baseos/command/egrep_tc001.sh | 91 +++ .../testcases/baseos/command/find_tc001.sh | 81 +++ .../baseos/command/fs_basic_tools_tc001.sh | 139 ++++ .../testcases/baseos/command/grep_tc001.sh | 113 ++++ .../testcases/baseos/command/gzip_tc001.sh | 79 +++ .../testcases/baseos/command/head_tc001.sh | 78 +++ .../testcases/baseos/command/kill_tc001.sh | 74 +++ .../testcases/baseos/command/less_tc001.sh | 66 ++ .../testcases/baseos/command/ln_tc001.sh | 99 +++ .../testcases/baseos/command/ls_tc001.sh | 79 +++ .../testcases/baseos/command/mkdir_tc001.sh | 83 +++ .../testcases/baseos/command/more_tc001.sh | 66 ++ .../testcases/baseos/command/mv_tc001.sh | 82 +++ .../testcases/baseos/command/nl_tc001.sh | 90 +++ .../testcases/baseos/command/pgrep_tc001.sh | 86 +++ .../testcases/baseos/command/ps_tc001.sh | 80 +++ .../testcases/baseos/command/renice_tc001.sh | 83 +++ .../testcases/baseos/command/rm_tc001.sh | 75 +++ .../testcases/baseos/command/sed_tc001.sh | 82 +++ .../testcases/baseos/command/sort_tc001.sh | 106 +++ .../testcases/baseos/command/stat_tc001.sh | 69 ++ .../testcases/baseos/command/tail_tc001.sh | 78 +++ .../testcases/baseos/command/tar_tc001.sh | 98 +++ .../baseos/command/text_tools_tc001.sh | 109 +++ .../testcases/baseos/command/touch_tc001.sh | 88 +++ .../testcases/baseos/command/tr_tc001.sh | 64 ++ .../testcases/baseos/command/uniq_tc001.sh | 90 +++ .../testcases/baseos/command/wc_tc001.sh | 95 +++ .../testcases/baseos/command/xz_tc001.sh | 78 +++ .../testcases/baseos/command/zip_tc001.sh | 83 +++ .../testcases/baseos/network/Makefile | 36 + .../baseos/network/dns_resolve_tc001.sh | 117 ++++ .../baseos/network/ip_tool_basic_tc001.sh | 138 ++++ .../testcases/baseos/network/netstat_tc001.sh | 56 ++ .../baseos/network/network_manager_tc001.sh | 128 ++++ .../baseos/network/nic_tool_cfg_tc001.sh | 129 ++++ .../testcases/baseos/network/ping_tc001.sh | 57 ++ .../baseos/network/tcp_listen_tc001.sh | 150 +++++ .../testcases/baseos/security/Makefile | 36 + .../security/acl_access_control_tc003.sh | 129 ++++ .../baseos/security/audit_file_ops_tc002.sh | 150 +++++ .../security/audit_identity_mac_tc004.sh | 151 +++++ .../baseos/security/audit_log_verify_tc003.sh | 159 +++++ .../baseos/security/audit_service_tc001.sh | 141 ++++ .../security/dac_access_control_tc002.sh | 126 ++++ .../security/firewall_port_access_tc003.sh | 130 ++++ .../security/firewalld_service_tc001.sh | 120 ++++ .../security/firewalld_zone_manage_tc004.sh | 157 +++++ .../baseos/security/iptables_rules_tc002.sh | 114 ++++ .../baseos/security/lsm_framework_tc001.sh | 120 ++++ .../baseos/security/passwd_crypt_cfg_tc001.sh | 101 +++ .../baseos/security/user_uid_unique_tc001.sh | 92 +++ .../testcases/baseos/service/Makefile | 36 + .../baseos/service/atd_service_tc006.sh | 157 +++++ .../baseos/service/chronyd_service_tc003.sh | 130 ++++ .../baseos/service/crond_service_tc002.sh | 132 ++++ .../baseos/service/dbus_service_tc001.sh | 141 ++++ .../service/irqbalance_service_tc007.sh | 153 +++++ .../baseos/service/journald_query_tc002.sh | 113 ++++ .../baseos/service/kernel_log_record_tc004.sh | 94 +++ .../service/log_access_control_tc005.sh | 113 ++++ .../baseos/service/logrotate_tc003.sh | 156 +++++ .../baseos/service/polkit_service_tc004.sh | 121 ++++ .../baseos/service/postfix_service_tc008.sh | 168 +++++ .../baseos/service/rsyslog_service_tc001.sh | 105 +++ .../baseos/service/snmp_tool_tc001.sh | 163 +++++ .../testcases/baseos/service/sshd_tc001.sh | 123 ++++ .../baseos/service/sysctl_tool_tc001.sh | 128 ++++ .../service/systemd_basic_func_tc001.sh | 130 ++++ .../service/systemd_custom_service_tc002.sh | 165 +++++ .../service/systemd_daemon_restart_tc003.sh | 123 ++++ .../baseos/service/tuned_service_tc005.sh | 161 +++++ .../testcases/baseos/systools/Makefile | 36 + .../baseos/systools/cpu_monitor_tool_tc001.sh | 154 +++++ .../baseos/systools/date_time_tool_tc001.sh | 169 +++++ .../testcases/baseos/systools/dracut_tc001.sh | 138 ++++ .../baseos/systools/hostname_tc001.sh | 57 ++ .../baseos/systools/hostnamectl_tc001.sh | 52 ++ .../baseos/systools/io_monitor_tool_tc003.sh | 163 +++++ .../testcases/baseos/systools/locale_tc001.sh | 67 ++ .../baseos/systools/localectl_tc001.sh | 63 ++ .../baseos/systools/mem_monitor_tool_tc002.sh | 174 +++++ .../baseos/systools/net_monitor_tool_tc004.sh | 147 ++++ .../baseos/systools/system_info_tc001.sh | 123 ++++ .../baseos/systools/tmux_tool_tc001.sh | 195 ++++++ os_smoke_src/testcases/baseos/user/Makefile | 36 + .../testcases/baseos/user/groupadd_tc001.sh | 70 ++ .../testcases/baseos/user/groupdel_tc001.sh | 72 ++ .../testcases/baseos/user/id_tc001.sh | 91 +++ .../testcases/baseos/user/useradd_tc001.sh | 88 +++ .../testcases/baseos/user/userdel_tc001.sh | 88 +++ .../testcases/baseos/user/usermod_tc001.sh | 100 +++ .../testcases/baseos/user/who_tc001.sh | 62 ++ .../testcases/baseos/user/whoami_tc001.sh | 67 ++ os_smoke_src/testcases/develop/Makefile | 22 + .../testcases/develop/buildtool/Makefile | 36 + .../develop/buildtool/autoconf_tc001.sh | 123 ++++ .../develop/buildtool/binutils_tc001.sh | 173 +++++ .../develop/buildtool/binutils_test/Makefile | 36 + .../buildtool/binutils_test/binutils_test.c | 36 + .../develop/buildtool/cmake_tc001.sh | 93 +++ .../buildtool/cmake_test/CMakeLists.txt | 3 + .../develop/buildtool/cmake_test/Makefile | 36 + .../develop/buildtool/cmake_test/cmake_test.c | 47 ++ .../develop/buildtool/cmake_test/strutil.c | 30 + .../testcases/develop/buildtool/git_tc001.sh | 109 +++ .../testcases/develop/buildtool/make_tc001.sh | 95 +++ .../develop/buildtool/make_test/Makefile | 34 + .../develop/buildtool/make_test/make_test.c | 43 ++ .../develop/buildtool/make_test/mathlib.c | 16 + .../develop/buildtool/rpmbuild_tc001.sh | 129 ++++ .../testcases/develop/compiler/Makefile | 37 ++ .../testcases/develop/compiler/clang_tc001.sh | 90 +++ .../testcases/develop/compiler/clang_test.c | 131 ++++ .../testcases/develop/compiler/gcc_tc001.sh | 78 +++ .../testcases/develop/compiler/gcc_test.c | 120 ++++ .../testcases/develop/compiler/gxx_tc001.sh | 78 +++ .../testcases/develop/compiler/gxx_test.cpp | 111 ++++ os_smoke_src/testcases/develop/debug/Makefile | 36 + os_smoke_src/testcases/develop/debug/attach.c | 19 + .../testcases/develop/debug/gdb_tc001.sh | 103 +++ .../testcases/develop/debug/gdb_test.ini | 14 + .../testcases/develop/debug/perf_tc001.sh | 75 +++ .../testcases/develop/debug/strace_tc001.sh | 74 +++ .../testcases/develop/debug/valgrind_tc001.sh | 91 +++ .../testcases/develop/editor/Makefile | 36 + .../testcases/develop/editor/emacs_tc001.sh | 74 +++ .../testcases/develop/editor/nano_tc001.sh | 63 ++ .../testcases/develop/editor/vim_tc001.sh | 79 +++ .../testcases/develop/graphlib/Makefile | 56 ++ .../testcases/develop/graphlib/cairo_tc001.sh | 95 +++ .../testcases/develop/graphlib/cairo_test.c | 184 +++++ .../testcases/develop/graphlib/gtk_tc001.sh | 83 +++ .../testcases/develop/graphlib/gtk_test.c | 134 ++++ .../develop/graphlib/opengl_tc001.sh | 78 +++ .../develop/graphlib/opengl_test.cpp | 45 ++ .../testcases/develop/graphlib/qt_tc001.sh | 103 +++ .../develop/graphlib/qt_test/.qmake.stash | 22 + .../develop/graphlib/qt_test/Makefile | 36 + .../develop/graphlib/qt_test/qt_test.cpp | 110 +++ .../testcases/develop/language/Makefile | 36 + .../develop/language/golang_tc001.sh | 99 +++ .../testcases/develop/language/gotest.go | 123 ++++ .../testcases/develop/language/java_tc001.sh | 90 +++ .../testcases/develop/language/java_test.java | 118 ++++ .../testcases/develop/language/js_test.js | 111 ++++ .../develop/language/nodejs_tc001.sh | 66 ++ .../testcases/develop/language/perl_tc001.sh | 65 ++ .../testcases/develop/language/pl_test.pl | 105 +++ .../testcases/develop/language/py2_test.py | 75 +++ .../testcases/develop/language/py3_test.py | 157 +++++ .../develop/language/python_tc001.sh | 76 +++ .../testcases/develop/language/rb_test.rb | 125 ++++ .../testcases/develop/language/rs_test.rs | 169 +++++ .../testcases/develop/language/ruby_tc001.sh | 66 ++ .../testcases/develop/language/rust_tc001.sh | 81 +++ os_smoke_src/testcases/driver/Makefile | 22 + os_smoke_src/testcases/kernel/Makefile | 22 + os_smoke_src/testcases/kernel/cpu/Makefile | 90 +++ .../testcases/kernel/cpu/cpu_cgroup_tc001.sh | 153 +++++ .../testcases/kernel/cpu/cpu_hotplug_tc001.sh | 89 +++ .../testcases/kernel/cpu/cpu_idle_tc001.sh | 94 +++ .../testcases/kernel/cpu/cpu_info_tc001.sh | 99 +++ .../testcases/kernel/cpu/cpu_isolate_tc001.sh | 93 +++ .../kernel/cpu/cpu_topology_tc001.sh | 91 +++ os_smoke_src/testcases/kernel/cpu/hw_rand | Bin 0 -> 21512 bytes .../testcases/kernel/cpu/hw_rand_tc001.c | 115 ++++ .../testcases/kernel/cpu/hw_rand_tc001.sh | 59 ++ .../kernel/cpu/mutex_protect_tc001.c | 187 ++++++ .../kernel/cpu/mutex_protect_tc001.sh | 55 ++ .../kernel/cpu/numa_access_cpu_tc001.c | 203 ++++++ .../kernel/cpu/numa_access_cpu_tc001.sh | 55 ++ .../testcases/kernel/cpu/numa_access_tc001.c | 133 ++++ .../testcases/kernel/cpu/numa_access_tc001.sh | 53 ++ .../testcases/kernel/cpu/soft_cryp_tc001.c | 177 +++++ .../testcases/kernel/cpu/soft_cryp_tc001.cfg | 1 + .../testcases/kernel/cpu/soft_cryp_tc001.sh | 56 ++ .../kernel/cpu/taskset_process_cpu_tc001.sh | 82 +++ .../kernel/cpu/taskset_process_cpus_tc001.sh | 98 +++ os_smoke_src/testcases/kernel/fs/Makefile | 36 + .../kernel/fs/block_vfs_ext4_tc001.sh | 65 ++ .../testcases/kernel/fs/ext2_tc001.sh | 90 +++ .../testcases/kernel/fs/ext3_tc001.sh | 90 +++ .../testcases/kernel/fs/ext4_tc001.sh | 90 +++ .../testcases/kernel/fs/fat32_tc001.sh | 90 +++ .../testcases/kernel/fs/fdisk_delete.ini | 14 + .../testcases/kernel/fs/fdisk_gpt_part.ini | 8 + .../testcases/kernel/fs/fdisk_new_part.ini | 13 + .../kernel/fs/fs_save_search_tc001.sh | 61 ++ .../kernel/fs/longest_filename_tc001.sh | 60 ++ .../testcases/kernel/fs/ntfs_tc001.sh | 92 +++ .../testcases/kernel/fs/vfat_tc001.sh | 90 +++ .../kernel/fs/vfs_stdin_stdout_tc001.sh | 65 ++ os_smoke_src/testcases/kernel/fs/xfs_tc001.sh | 93 +++ os_smoke_src/testcases/kernel/mem/Makefile | 45 ++ .../testcases/kernel/mem/huge_page_tc001.c | 137 ++++ .../testcases/kernel/mem/huge_page_tc001.sh | 96 +++ .../testcases/kernel/mem/numa_aware_tc001.c | 25 + .../testcases/kernel/mem/numa_aware_tc001.sh | 82 +++ os_smoke_src/testcases/kernel/net/Makefile | 36 + .../testcases/kernel/net/bond_tc001.sh | 95 +++ .../testcases/kernel/net/ethtool_tc001.sh | 93 +++ .../testcases/kernel/net/ip_link_tc001.sh | 78 +++ .../testcases/kernel/net/ip_route_tc001.sh | 63 ++ .../testcases/kernel/net/ipv4_tc001.sh | 77 +++ .../testcases/kernel/net/ipv6_tc001.sh | 76 +++ .../testcases/kernel/net/net_link_tc001.sh | 80 +++ .../testcases/kernel/net/nettools_tc001.sh | 74 +++ .../testcases/kernel/net/network_libs.sh | 573 ++++++++++++++++ .../kernel/net/tcp_concurrent_client.py | 84 +++ .../kernel/net/tcp_concurrent_tc003.sh | 121 ++++ .../testcases/kernel/net/tcp_echo_server.py | 50 ++ .../testcases/kernel/net/tcp_echo_tc002.sh | 97 +++ .../testcases/kernel/net/tcp_http_server.py | 36 + .../testcases/kernel/net/tcp_iperf_tc001.sh | 99 +++ .../testcases/kernel/net/tcp_monitor_tc005.sh | 142 ++++ .../net/tcp_segmentation_offload_tc001.sh | 111 ++++ .../testcases/kernel/net/tcp_tuning_tc004.sh | 140 ++++ .../testcases/kernel/net/udp_client.py | 35 + .../testcases/kernel/net/udp_echo_server.py | 35 + .../testcases/kernel/net/udp_echo_tc002.sh | 103 +++ .../testcases/kernel/net/udp_iperf_tc001.sh | 104 +++ .../kernel/net/udp_lossy_perf_tc004.sh | 103 +++ .../testcases/kernel/net/udp_monitor_tc005.sh | 128 ++++ .../kernel/net/udp_packet_size_tc003.sh | 118 ++++ .../testcases/kernel/net/udp_perf_server.py | 56 ++ os_smoke_src/testcases/kernel/sched/Makefile | 36 + .../kernel/sched/auto_load_balance_tc001.sh | 71 ++ .../kernel/sched/cpu_sched_numabind_tc001.sh | 77 +++ .../sched/process_load_balance_tc001.sh | 59 ++ .../kernel/sched/process_renice_tc001.sh | 81 +++ .../kernel/sched/process_rt_tc001.sh | 101 +++ .../kernel/sched/task_status_check_tc001.sh | 92 +++ .../testcases/kernel/storage/Makefile | 36 + .../kernel/storage/fs_share_target_tc001.sh | 83 +++ .../storage/lvm_adjust_partition_tc001.sh | 103 +++ .../kernel/storage/nvme_swap_tc001.sh | 94 +++ .../storage/slow_device_caches_tc001.sh | 92 +++ .../kernel/storage/soft_raid0_tc001.sh | 75 +++ .../kernel/storage/soft_raid10_tc001.sh | 78 +++ .../kernel/storage/soft_raid1_tc001.sh | 75 +++ .../kernel/storage/soft_raid5_tc001.sh | 77 +++ .../kernel/storage/soft_raid6_tc001.sh | 78 +++ .../testcases/kernel/storage/swap_tc001.sh | 77 +++ post_uninit.sh | 15 + pre_init.sh | 16 + tools/case_lib.c | 429 ++++++++++++ tools/case_lib.py | 482 ++++++++++++++ tools/case_lib.sh | 439 ++++++++++++ tools/tos_test.sh | 629 ++++++++++++++++++ 277 files changed, 26314 insertions(+), 44 deletions(-) create mode 100755 baseline/cases create mode 100755 baseline/cases_app create mode 100755 baseline/cases_baseos create mode 100755 baseline/cases_develop create mode 100644 baseline/cases_driver create mode 100755 baseline/cases_kernel create mode 100755 control create mode 100755 os_smoke_src/Makefile create mode 100755 os_smoke_src/lib/Makefile create mode 100755 os_smoke_src/lib/common_libs.sh create mode 100755 os_smoke_src/testcases/Makefile create mode 100755 os_smoke_src/testcases/app/Makefile create mode 100755 os_smoke_src/testcases/baseos/Makefile create mode 100755 os_smoke_src/testcases/baseos/command/Makefile create mode 100755 os_smoke_src/testcases/baseos/command/awk_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/bzip2_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/cat_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/chmod_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/chown_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/cp_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/cut_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/dd_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/df_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/du_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/egrep_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/find_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/fs_basic_tools_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/grep_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/gzip_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/head_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/kill_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/less_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/ln_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/ls_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/mkdir_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/more_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/mv_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/nl_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/pgrep_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/ps_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/renice_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/rm_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/sed_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/sort_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/stat_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/tail_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/tar_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/text_tools_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/touch_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/tr_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/uniq_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/wc_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/xz_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/command/zip_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/network/Makefile create mode 100755 os_smoke_src/testcases/baseos/network/dns_resolve_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/network/ip_tool_basic_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/network/netstat_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/network/network_manager_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/network/nic_tool_cfg_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/network/ping_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/network/tcp_listen_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/security/Makefile create mode 100755 os_smoke_src/testcases/baseos/security/acl_access_control_tc003.sh create mode 100755 os_smoke_src/testcases/baseos/security/audit_file_ops_tc002.sh create mode 100755 os_smoke_src/testcases/baseos/security/audit_identity_mac_tc004.sh create mode 100755 os_smoke_src/testcases/baseos/security/audit_log_verify_tc003.sh create mode 100755 os_smoke_src/testcases/baseos/security/audit_service_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/security/dac_access_control_tc002.sh create mode 100755 os_smoke_src/testcases/baseos/security/firewall_port_access_tc003.sh create mode 100755 os_smoke_src/testcases/baseos/security/firewalld_service_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/security/firewalld_zone_manage_tc004.sh create mode 100755 os_smoke_src/testcases/baseos/security/iptables_rules_tc002.sh create mode 100755 os_smoke_src/testcases/baseos/security/lsm_framework_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/security/passwd_crypt_cfg_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/security/user_uid_unique_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/service/Makefile create mode 100755 os_smoke_src/testcases/baseos/service/atd_service_tc006.sh create mode 100755 os_smoke_src/testcases/baseos/service/chronyd_service_tc003.sh create mode 100755 os_smoke_src/testcases/baseos/service/crond_service_tc002.sh create mode 100755 os_smoke_src/testcases/baseos/service/dbus_service_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/service/irqbalance_service_tc007.sh create mode 100755 os_smoke_src/testcases/baseos/service/journald_query_tc002.sh create mode 100755 os_smoke_src/testcases/baseos/service/kernel_log_record_tc004.sh create mode 100755 os_smoke_src/testcases/baseos/service/log_access_control_tc005.sh create mode 100755 os_smoke_src/testcases/baseos/service/logrotate_tc003.sh create mode 100755 os_smoke_src/testcases/baseos/service/polkit_service_tc004.sh create mode 100755 os_smoke_src/testcases/baseos/service/postfix_service_tc008.sh create mode 100755 os_smoke_src/testcases/baseos/service/rsyslog_service_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/service/snmp_tool_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/service/sshd_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/service/sysctl_tool_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/service/systemd_basic_func_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/service/systemd_custom_service_tc002.sh create mode 100755 os_smoke_src/testcases/baseos/service/systemd_daemon_restart_tc003.sh create mode 100755 os_smoke_src/testcases/baseos/service/tuned_service_tc005.sh create mode 100755 os_smoke_src/testcases/baseos/systools/Makefile create mode 100755 os_smoke_src/testcases/baseos/systools/cpu_monitor_tool_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/systools/date_time_tool_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/systools/dracut_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/systools/hostname_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/systools/hostnamectl_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/systools/io_monitor_tool_tc003.sh create mode 100755 os_smoke_src/testcases/baseos/systools/locale_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/systools/localectl_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/systools/mem_monitor_tool_tc002.sh create mode 100755 os_smoke_src/testcases/baseos/systools/net_monitor_tool_tc004.sh create mode 100755 os_smoke_src/testcases/baseos/systools/system_info_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/systools/tmux_tool_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/user/Makefile create mode 100755 os_smoke_src/testcases/baseos/user/groupadd_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/user/groupdel_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/user/id_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/user/useradd_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/user/userdel_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/user/usermod_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/user/who_tc001.sh create mode 100755 os_smoke_src/testcases/baseos/user/whoami_tc001.sh create mode 100755 os_smoke_src/testcases/develop/Makefile create mode 100755 os_smoke_src/testcases/develop/buildtool/Makefile create mode 100755 os_smoke_src/testcases/develop/buildtool/autoconf_tc001.sh create mode 100755 os_smoke_src/testcases/develop/buildtool/binutils_tc001.sh create mode 100755 os_smoke_src/testcases/develop/buildtool/binutils_test/Makefile create mode 100755 os_smoke_src/testcases/develop/buildtool/binutils_test/binutils_test.c create mode 100755 os_smoke_src/testcases/develop/buildtool/cmake_tc001.sh create mode 100755 os_smoke_src/testcases/develop/buildtool/cmake_test/CMakeLists.txt create mode 100755 os_smoke_src/testcases/develop/buildtool/cmake_test/Makefile create mode 100755 os_smoke_src/testcases/develop/buildtool/cmake_test/cmake_test.c create mode 100644 os_smoke_src/testcases/develop/buildtool/cmake_test/strutil.c create mode 100755 os_smoke_src/testcases/develop/buildtool/git_tc001.sh create mode 100755 os_smoke_src/testcases/develop/buildtool/make_tc001.sh create mode 100755 os_smoke_src/testcases/develop/buildtool/make_test/Makefile create mode 100755 os_smoke_src/testcases/develop/buildtool/make_test/make_test.c create mode 100644 os_smoke_src/testcases/develop/buildtool/make_test/mathlib.c create mode 100755 os_smoke_src/testcases/develop/buildtool/rpmbuild_tc001.sh create mode 100755 os_smoke_src/testcases/develop/compiler/Makefile create mode 100755 os_smoke_src/testcases/develop/compiler/clang_tc001.sh create mode 100755 os_smoke_src/testcases/develop/compiler/clang_test.c create mode 100755 os_smoke_src/testcases/develop/compiler/gcc_tc001.sh create mode 100755 os_smoke_src/testcases/develop/compiler/gcc_test.c create mode 100755 os_smoke_src/testcases/develop/compiler/gxx_tc001.sh create mode 100755 os_smoke_src/testcases/develop/compiler/gxx_test.cpp create mode 100755 os_smoke_src/testcases/develop/debug/Makefile create mode 100755 os_smoke_src/testcases/develop/debug/attach.c create mode 100755 os_smoke_src/testcases/develop/debug/gdb_tc001.sh create mode 100755 os_smoke_src/testcases/develop/debug/gdb_test.ini create mode 100755 os_smoke_src/testcases/develop/debug/perf_tc001.sh create mode 100755 os_smoke_src/testcases/develop/debug/strace_tc001.sh create mode 100755 os_smoke_src/testcases/develop/debug/valgrind_tc001.sh create mode 100755 os_smoke_src/testcases/develop/editor/Makefile create mode 100755 os_smoke_src/testcases/develop/editor/emacs_tc001.sh create mode 100755 os_smoke_src/testcases/develop/editor/nano_tc001.sh create mode 100755 os_smoke_src/testcases/develop/editor/vim_tc001.sh create mode 100755 os_smoke_src/testcases/develop/graphlib/Makefile create mode 100755 os_smoke_src/testcases/develop/graphlib/cairo_tc001.sh create mode 100755 os_smoke_src/testcases/develop/graphlib/cairo_test.c create mode 100755 os_smoke_src/testcases/develop/graphlib/gtk_tc001.sh create mode 100755 os_smoke_src/testcases/develop/graphlib/gtk_test.c create mode 100755 os_smoke_src/testcases/develop/graphlib/opengl_tc001.sh create mode 100755 os_smoke_src/testcases/develop/graphlib/opengl_test.cpp create mode 100755 os_smoke_src/testcases/develop/graphlib/qt_tc001.sh create mode 100755 os_smoke_src/testcases/develop/graphlib/qt_test/.qmake.stash create mode 100755 os_smoke_src/testcases/develop/graphlib/qt_test/Makefile create mode 100755 os_smoke_src/testcases/develop/graphlib/qt_test/qt_test.cpp create mode 100755 os_smoke_src/testcases/develop/language/Makefile create mode 100755 os_smoke_src/testcases/develop/language/golang_tc001.sh create mode 100755 os_smoke_src/testcases/develop/language/gotest.go create mode 100755 os_smoke_src/testcases/develop/language/java_tc001.sh create mode 100755 os_smoke_src/testcases/develop/language/java_test.java create mode 100755 os_smoke_src/testcases/develop/language/js_test.js create mode 100755 os_smoke_src/testcases/develop/language/nodejs_tc001.sh create mode 100755 os_smoke_src/testcases/develop/language/perl_tc001.sh create mode 100755 os_smoke_src/testcases/develop/language/pl_test.pl create mode 100755 os_smoke_src/testcases/develop/language/py2_test.py create mode 100755 os_smoke_src/testcases/develop/language/py3_test.py create mode 100755 os_smoke_src/testcases/develop/language/python_tc001.sh create mode 100755 os_smoke_src/testcases/develop/language/rb_test.rb create mode 100755 os_smoke_src/testcases/develop/language/rs_test.rs create mode 100755 os_smoke_src/testcases/develop/language/ruby_tc001.sh create mode 100755 os_smoke_src/testcases/develop/language/rust_tc001.sh create mode 100755 os_smoke_src/testcases/driver/Makefile create mode 100755 os_smoke_src/testcases/kernel/Makefile create mode 100755 os_smoke_src/testcases/kernel/cpu/Makefile create mode 100755 os_smoke_src/testcases/kernel/cpu/cpu_cgroup_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/cpu/cpu_hotplug_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/cpu/cpu_idle_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/cpu/cpu_info_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/cpu/cpu_isolate_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/cpu/cpu_topology_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/cpu/hw_rand create mode 100755 os_smoke_src/testcases/kernel/cpu/hw_rand_tc001.c create mode 100755 os_smoke_src/testcases/kernel/cpu/hw_rand_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/cpu/mutex_protect_tc001.c create mode 100755 os_smoke_src/testcases/kernel/cpu/mutex_protect_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/cpu/numa_access_cpu_tc001.c create mode 100755 os_smoke_src/testcases/kernel/cpu/numa_access_cpu_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/cpu/numa_access_tc001.c create mode 100755 os_smoke_src/testcases/kernel/cpu/numa_access_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/cpu/soft_cryp_tc001.c create mode 100755 os_smoke_src/testcases/kernel/cpu/soft_cryp_tc001.cfg create mode 100755 os_smoke_src/testcases/kernel/cpu/soft_cryp_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/cpu/taskset_process_cpu_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/cpu/taskset_process_cpus_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/fs/Makefile create mode 100755 os_smoke_src/testcases/kernel/fs/block_vfs_ext4_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/fs/ext2_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/fs/ext3_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/fs/ext4_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/fs/fat32_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/fs/fdisk_delete.ini create mode 100755 os_smoke_src/testcases/kernel/fs/fdisk_gpt_part.ini create mode 100755 os_smoke_src/testcases/kernel/fs/fdisk_new_part.ini create mode 100755 os_smoke_src/testcases/kernel/fs/fs_save_search_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/fs/longest_filename_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/fs/ntfs_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/fs/vfat_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/fs/vfs_stdin_stdout_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/fs/xfs_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/mem/Makefile create mode 100755 os_smoke_src/testcases/kernel/mem/huge_page_tc001.c create mode 100755 os_smoke_src/testcases/kernel/mem/huge_page_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/mem/numa_aware_tc001.c create mode 100755 os_smoke_src/testcases/kernel/mem/numa_aware_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/net/Makefile create mode 100755 os_smoke_src/testcases/kernel/net/bond_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/net/ethtool_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/net/ip_link_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/net/ip_route_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/net/ipv4_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/net/ipv6_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/net/net_link_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/net/nettools_tc001.sh create mode 100644 os_smoke_src/testcases/kernel/net/network_libs.sh create mode 100644 os_smoke_src/testcases/kernel/net/tcp_concurrent_client.py create mode 100755 os_smoke_src/testcases/kernel/net/tcp_concurrent_tc003.sh create mode 100644 os_smoke_src/testcases/kernel/net/tcp_echo_server.py create mode 100755 os_smoke_src/testcases/kernel/net/tcp_echo_tc002.sh create mode 100644 os_smoke_src/testcases/kernel/net/tcp_http_server.py create mode 100755 os_smoke_src/testcases/kernel/net/tcp_iperf_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/net/tcp_monitor_tc005.sh create mode 100755 os_smoke_src/testcases/kernel/net/tcp_segmentation_offload_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/net/tcp_tuning_tc004.sh create mode 100755 os_smoke_src/testcases/kernel/net/udp_client.py create mode 100755 os_smoke_src/testcases/kernel/net/udp_echo_server.py create mode 100755 os_smoke_src/testcases/kernel/net/udp_echo_tc002.sh create mode 100755 os_smoke_src/testcases/kernel/net/udp_iperf_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/net/udp_lossy_perf_tc004.sh create mode 100755 os_smoke_src/testcases/kernel/net/udp_monitor_tc005.sh create mode 100755 os_smoke_src/testcases/kernel/net/udp_packet_size_tc003.sh create mode 100644 os_smoke_src/testcases/kernel/net/udp_perf_server.py create mode 100755 os_smoke_src/testcases/kernel/sched/Makefile create mode 100755 os_smoke_src/testcases/kernel/sched/auto_load_balance_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/sched/cpu_sched_numabind_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/sched/process_load_balance_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/sched/process_renice_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/sched/process_rt_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/sched/task_status_check_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/storage/Makefile create mode 100755 os_smoke_src/testcases/kernel/storage/fs_share_target_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/storage/lvm_adjust_partition_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/storage/nvme_swap_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/storage/slow_device_caches_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/storage/soft_raid0_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/storage/soft_raid10_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/storage/soft_raid1_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/storage/soft_raid5_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/storage/soft_raid6_tc001.sh create mode 100755 os_smoke_src/testcases/kernel/storage/swap_tc001.sh create mode 100755 post_uninit.sh create mode 100755 pre_init.sh create mode 100644 tools/case_lib.c create mode 100755 tools/case_lib.py create mode 100755 tools/case_lib.sh create mode 100755 tools/tos_test.sh diff --git a/LICENSE b/LICENSE index d159169..25e216a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ - GNU GENERAL PUBLIC LICENSE +GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., @@ -336,4 +336,4 @@ This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. +Public License instead of this License. \ No newline at end of file diff --git a/README.en.md b/README.en.md index 1fb18fd..5cfd548 100644 --- a/README.en.md +++ b/README.en.md @@ -1,36 +1,203 @@ -# Smoke Test +# OS Smoke #### Description -Smoke-test 是一套轻量级、自动化的操作系统冒烟测试工具。它主要用于在操作系统镜像构建完成后,第一时间对系统进行“健康检查”。 + +OS Smoke is a lightweight, automated OS smoke testing tool. It is designed to perform a quick "health check" immediately after an OS image is built, verifying that critical components such as the kernel, base system, drivers, applications, and development tools are functioning properly. #### Software Architecture -Software architecture description + +``` +os_smoke/ +├── control # Test suite configuration (name, deps, build commands) +├── pre_init.sh # Pre-hooks (called before compile/test) +├── post_uninit.sh # Post-hooks (called after compile/test) +├── baseline/ # Test case baseline definitions +│ ├── cases # Full test case list +│ ├── cases_kernel # Kernel test cases +│ ├── cases_baseos # Base OS test cases +│ ├── cases_app # Application test cases +│ ├── cases_develop # Development tool test cases +│ └── cases_driver # Driver test cases +├── tools/ # Core test framework +│ ├── tos_test.sh # Test execution engine (main dispatcher) +│ ├── case_lib.sh # Shell test framework library +│ ├── case_lib.py # Python test framework library +│ └── case_lib.c # C test framework library +└── os_smoke_src/ # Test suite source code + ├── Makefile # Top-level build file + ├── lib/ + │ └── common_libs.sh # Common library functions (package management, etc.) + └── testcases/ # Test case directories + ├── kernel/ # Kernel cases + ├── baseos/ # Base OS cases + ├── app/ # Application cases + ├── develop/ # Development tool cases + └── driver/ # Driver cases +``` + +The project provides unified test framework libraries in C, Python, and Shell (`tools/case_lib.*`), supporting consistent result reporting (`TPASS/TFAIL/TCONF/TWARN/TINFO`), safe file operations, and virtual machine detection. #### Installation -1. xxxx -2. xxxx -3. xxxx +1. Clone the repository to the target test machine: + ```bash + git clone + cd os_smoke + ``` + +2. Install test suite dependencies: + ```bash + ./tools/tos_test.sh pkg + ``` + +3. Compile and install the test suite: + ```bash + ./tools/tos_test.sh compile + ``` #### Instructions -1. xxxx -2. xxxx -3. xxxx +1. Run the full smoke test: + ```bash + ./tools/tos_test.sh test + ``` -#### Contribution +2. Run tests by category (supports kernel, baseos, app, develop, driver): + ```bash + ./tools/tos_test.sh test kernel + ``` + +3. Run a single test case: + ```bash + ./tools/tos_test.sh case + ``` + +4. Clean build artifacts: + ```bash + ./tools/tos_test.sh clean + ``` + +#### Result Query and Log Analysis + +After testing, all logs and result files are stored in `/data/log/os_suites/os_smoke/`: + +| File | Description | +|---|---| +| `os_smoke.result` | Summary result file, each line formatted as `PASS/FAIL: case_name time(seconds)` | +| `os_smoke.log` | Runtime log with detailed output from compilation and execution | +| `os_smoke.log.compile` | Compilation phase log (generated by `compile` command) | +| `` | Individual log file for each test case | +| `.dmesg` | Kernel dmesg snapshot, auto-saved when kernel anomalies are detected | + +Query commands: + +```bash +# View overall pass/fail summary +cat /data/log/os_suites/os_smoke/os_smoke.result + +# Filter all failed cases +grep "^FAIL" /data/log/os_suites/os_smoke/os_smoke.result + +# View detailed log of a specific failed case +cat /data/log/os_suites/os_smoke/ + +# Check for kernel anomalies (dmesg snapshots) +ls /data/log/os_suites/os_smoke/*.dmesg 2>/dev/null + +# Count pass/fail totals +echo "PASS: $(grep -c '^PASS' /data/log/os_suites/os_smoke/os_smoke.result)" +echo "FAIL: $(grep -c '^FAIL' /data/log/os_suites/os_smoke/os_smoke.result)" +``` + +When running tests by category, the result file is named `os_smoke_.result` (e.g., `os_smoke_kernel.result`). + +#### Adding Test Cases + +1. Create a case directory and script under `os_smoke_src/testcases//` +2. In the test script, source the framework library and use the API to report results: + ```bash + source case_lib.sh -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request + # Report test passed + tst_res $TPASS "Feature X verified successfully" + # Report test failed + tst_res $TFAIL "Feature X returned unexpected value" -#### Gitee Feature + # Skip test (environment not met, etc.) + tst_res $TCONF "Missing dependency, skipping test" + + # Report warning (non-fatal) + tst_res $TWARN "Performance below expected threshold" + + # Output informational message (does not affect result) + tst_res $TINFO "Current kernel version: $(uname -r)" + + # Fatal error, abort test case immediately + tst_brk $TFAIL "Failed to create test directory" + tst_brk $TCONF "Unsupported architecture, skipping" + + # Safe command execution (auto-aborts on failure) + SAFE_CMD "Create temp directory" mkdir -p /tmp/test_dir + ``` +3. Register the case in `baseline/cases` and the corresponding category file, using the format: + ``` + case_name; cd os_smoke/testcases/category/subdir; ./case_script.sh + ``` + +#### Other + +##### 1. Environment Cleanup + +Clean log files: +```bash +# Backup old logs (recommended) +timestamp=$(date +%Y%m%d_%H%M%S) +mv /data/log/os_suites/os_smoke /data/log/os_suites/os_smoke.bak_${timestamp} + +# Or delete old logs directly +rm -rf /data/log/os_suites/os_smoke/* +``` + +##### 2. Custom Configuration + +Modify timeout: +```bash +# Edit the control file +vim control + +# Add or modify timeout configuration +CONTROL_CASE_TIMEOUT=3600 # Set to 1 hour +``` + +Add custom dependency packages: +```bash +# Edit the control file +vim control + +# Modify dependency package list (note: only common packages are configured here) +CONTROL_PRE_PKG='gcc automake expect kernel-devel your_package' +``` + +##### 3. Disk Device Configuration + +Configure real disk devices (optional): +```bash +# Edit the control file +vim control + +# Add disk device configuration (if the system has spare disks) +CONTROL_CASE_DISK_DEVICE="/dev/sdb" + +# Multiple disk devices +CONTROL_CASE_DISK_DEVICE="/dev/sdb /dev/sdc" +``` + +**Note**: If this is not configured, disk-related tests will automatically use loopback devices for simulation. + +#### Contribution -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +1. Fork the repository +2. Create a Feat\_xxx branch +3. Commit your code +4. Create a Pull Request diff --git a/README.md b/README.md index d703eaf..c0302db 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,203 @@ -# Smoke Test +# OS Smoke #### 介绍 -Smoke-test 是一套轻量级、自动化的操作系统冒烟测试工具。它主要用于在操作系统镜像构建完成后,第一时间对系统进行“健康检查”。 + +OS Smoke 是一套轻量级、自动化的操作系统冒烟测试工具。它主要用于在操作系统镜像构建完成后,第一时间对系统进行"健康检查",快速验证内核、基础系统、驱动、应用、开发工具等关键组件是否正常工作。 #### 软件架构 -软件架构说明 +``` +os_smoke/ +├── control # 测试套配置文件(名称、依赖、编译命令等) +├── pre_init.sh # 前置钩子(编译前/测试前调用) +├── post_uninit.sh # 后置钩子(编译后/测试后调用) +├── baseline/ # 用例基线定义 +│ ├── cases # 全量用例列表 +│ ├── cases_kernel # 内核测试用例 +│ ├── cases_baseos # 基础系统测试用例 +│ ├── cases_app # 应用测试用例 +│ ├── cases_develop # 开发工具测试用例 +│ └── cases_driver # 第三方驱动测试用例 +├── tools/ # 测试框架核心库 +│ ├── tos_test.sh # 测试执行引擎(总调度器) +│ ├── case_lib.sh # Shell 测试框架库 +│ ├── case_lib.py # Python 测试框架库 +│ └── case_lib.c # C 测试框架库 +└── os_smoke_src/ # 测试套源码 + ├── Makefile # 顶层构建文件 + ├── lib/ + │ └── common_libs.sh # 公共库函数(包管理等) + └── testcases/ # 测试用例目录 + ├── kernel/ # 内核用例 + ├── baseos/ # 基础系统用例 + ├── app/ # 应用用例 + ├── develop/ # 开发工具用例 + └── driver/ # 第三方驱动用例 +``` + +项目提供 C、Python、Shell 三种语言的统一测试框架库(`tools/case_lib.*`),支持统一的结果上报(`TPASS/TFAIL/TCONF/TWARN/TINFO`)、安全文件操作、虚拟机检测等能力。 #### 安装教程 -1. xxxx -2. xxxx -3. xxxx +1. 克隆仓库到目标测试机器: + ```bash + git clone <仓库地址> + cd os_smoke + ``` + +2. 安装测试套依赖软件包: + ```bash + ./tools/tos_test.sh pkg + ``` + +3. 编译并安装测试套: + ```bash + ./tools/tos_test.sh compile + ``` #### 使用说明 -1. xxxx -2. xxxx -3. xxxx +1. 执行全量冒烟测试: + ```bash + ./tools/tos_test.sh test + ``` -#### 参与贡献 +2. 按类别执行测试(支持 kernel、baseos、app、develop、driver): + ```bash + ./tools/tos_test.sh test kernel + ``` + +3. 执行单个测试用例: + ```bash + ./tools/tos_test.sh case <用例名称> + ``` + +4. 清理编译产物: + ```bash + ./tools/tos_test.sh clean + ``` + +#### 结果查询与日志分析 + +测试完成后,所有日志和结果文件存放在 `/data/log/os_suites/os_smoke/` 目录下: + +| 文件 | 说明 | +|---|---| +| `os_smoke.result` | 汇总结果文件,每行格式为 `PASS/FAIL: 用例名 耗时(秒)` | +| `os_smoke.log` | 运行日志,包含编译、执行过程中的详细输出 | +| `os_smoke.log.compile` | 编译阶段日志(`compile` 命令产生) | +| `<用例名>` | 每个用例的独立日志文件 | +| `<用例名>.dmesg` | 用例执行期间若检测到内核异常,自动保存的 dmesg 快照 | + +查询命令: + +```bash +# 查看整体通过/失败汇总 +cat /data/log/os_suites/os_smoke/os_smoke.result + +# 筛选所有失败用例 +grep "^FAIL" /data/log/os_suites/os_smoke/os_smoke.result + +# 查看某个失败用例的详细日志 +cat /data/log/os_suites/os_smoke/<用例名> + +# 检查是否有内核异常(dmesg 快照) +ls /data/log/os_suites/os_smoke/*.dmesg 2>/dev/null + +# 统计通过/失败数量 +echo "PASS: $(grep -c '^PASS' /data/log/os_suites/os_smoke/os_smoke.result)" +echo "FAIL: $(grep -c '^FAIL' /data/log/os_suites/os_smoke/os_smoke.result)" +``` + +按类别执行测试时,结果文件为 `os_smoke_<类别>.result`(如 `os_smoke_kernel.result`)。 + +#### 添加测试用例 + +1. 在 `os_smoke_src/testcases/<类别>/` 下创建用例目录和脚本 +2. 在用例脚本中引用框架库并使用 API 上报结果: + ```bash + source case_lib.sh + + # 上报测试通过 + tst_res $TPASS "功能X验证通过" -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request + # 上报测试失败 + tst_res $TFAIL "功能X返回值异常" + # 跳过测试(环境不满足等) + tst_res $TCONF "缺少依赖包,跳过测试" -#### 特技 + # 输出警告信息(非致命) + tst_res $TWARN "性能低于预期阈值" + + # 输出提示信息(不影响结果) + tst_res $TINFO "当前内核版本: $(uname -r)" + + # 致命错误,立即中止用例 + tst_brk $TFAIL "无法创建测试目录" + tst_brk $TCONF "不支持的架构,跳过" + + # 安全执行命令(失败时自动中止) + SAFE_CMD "创建临时目录" mkdir -p /tmp/test_dir + ``` +3. 在 `baseline/cases` 及对应分类文件中注册用例,格式为: + ``` + 用例名; cd os_smoke/testcases/分类/子目录; ./用例脚本.sh + ``` + +#### 其他 + +##### 1. 环境清理 + +清理日志文件: +```bash +# 备份旧日志(推荐) +timestamp=$(date +%Y%m%d_%H%M%S) +mv /data/log/os_suites/os_smoke /data/log/os_suites/os_smoke.bak_${timestamp} + +# 或直接删除旧日志 +rm -rf /data/log/os_suites/os_smoke/* +``` + +##### 2. 自定义配置 + +修改超时时间: +```bash +# 编辑 control 文件 +vim control + +# 添加或修改超时配置 +CONTROL_CASE_TIMEOUT=3600 # 设置为1小时 +``` + +添加自定义依赖包: +```bash +# 编辑 control 文件 +vim control + +# 修改依赖包列表(注意:此处配置为通用包) +CONTROL_PRE_PKG='gcc automake expect kernel-devel 你的包名' +``` + +##### 3. 磁盘设备配置 + +配置真实磁盘设备(可选): +```bash +# 编辑 control 文件 +vim control + +# 添加磁盘设备配置(如果系统有多余磁盘) +CONTROL_CASE_DISK_DEVICE="/dev/sdb" + +# 多个磁盘设备 +CONTROL_CASE_DISK_DEVICE="/dev/sdb /dev/sdc" +``` + +**说明**: 如果不配置此项,磁盘相关测试会自动使用 loopback 设备模拟。 + +#### 参与贡献 -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +1. Fork 本仓库 +2. 新建 Feat\_xxx 分支 +3. 提交代码 +4. 新建 Pull Request diff --git a/baseline/cases b/baseline/cases new file mode 100755 index 0000000..8f19bd1 --- /dev/null +++ b/baseline/cases @@ -0,0 +1,225 @@ +## cpu func +cpu_info_tc001; cd os_smoke/testcases/kernel/cpu; ./cpu_info_tc001.sh +cpu_topology_tc001; cd os_smoke/testcases/kernel/cpu; ./cpu_topology_tc001.sh +cpu_hotplug_tc001; cd os_smoke/testcases/kernel/cpu; ./cpu_hotplug_tc001.sh +cpu_isolate_tc001; cd os_smoke/testcases/kernel/cpu; ./cpu_isolate_tc001.sh +cpu_cgroup_tc001; cd os_smoke/testcases/kernel/cpu; ./cpu_cgroup_tc001.sh +cpu_idle_tc001; cd os_smoke/testcases/kernel/cpu; ./cpu_idle_tc001.sh +taskset_process_cpu_tc001; cd os_smoke/testcases/kernel/cpu; ./taskset_process_cpu_tc001.sh +taskset_process_cpus_tc001; cd os_smoke/testcases/kernel/cpu; ./taskset_process_cpus_tc001.sh +numa_access_tc001; cd os_smoke/testcases/kernel/cpu; ./numa_access_tc001.sh +numa_access_cpu_tc001; cd os_smoke/testcases/kernel/cpu; ./numa_access_cpu_tc001.sh +hw_rand_tc001; cd os_smoke/testcases/kernel/cpu; ./hw_rand_tc001.sh +mutex_protect_tc001; cd os_smoke/testcases/kernel/cpu; ./mutex_protect_tc001.sh +soft_cryp_tc001; cd os_smoke/testcases/kernel/cpu; ./soft_cryp_tc001.sh + +## cpu sched +auto_load_balance_tc001; cd os_smoke/testcases/kernel/sched; ./auto_load_balance_tc001.sh +process_load_balance_tc001; cd os_smoke/testcases/kernel/sched; ./process_load_balance_tc001.sh +cpu_sched_numabind_tc001; cd os_smoke/testcases/kernel/sched; ./cpu_sched_numabind_tc001.sh +process_renice_tc001; cd os_smoke/testcases/kernel/sched; ./process_renice_tc001.sh +process_rt_tc001; cd os_smoke/testcases/kernel/sched; ./process_rt_tc001.sh +task_status_check_tc001; cd os_smoke/testcases/kernel/sched; ./task_status_check_tc001.sh + +## mem +huge_page_tc001; cd os_smoke/testcases/kernel/mem; ./huge_page_tc001.sh +numa_aware_tc001; cd os_smoke/testcases/kernel/mem; ./numa_aware_tc001.sh + +## storage +soft_raid0_tc001; cd os_smoke/testcases/kernel/storage; ./soft_raid0_tc001.sh +soft_raid1_tc001; cd os_smoke/testcases/kernel/storage; ./soft_raid1_tc001.sh +soft_raid5_tc001; cd os_smoke/testcases/kernel/storage; ./soft_raid5_tc001.sh +soft_raid6_tc001; cd os_smoke/testcases/kernel/storage; ./soft_raid6_tc001.sh +soft_raid10_tc001; cd os_smoke/testcases/kernel/storage; ./soft_raid10_tc001.sh +swap_tc001; cd os_smoke/testcases/kernel/storage; ./swap_tc001.sh +slow_device_caches_tc001; cd os_smoke/testcases/kernel/storage; ./slow_device_caches_tc001.sh +nvme_swap_tc001; cd os_smoke/testcases/kernel/storage; ./nvme_swap_tc001.sh +lvm_adjust_partition_tc001; cd os_smoke/testcases/kernel/storage; ./lvm_adjust_partition_tc001.sh +fs_share_target_tc001; cd os_smoke/testcases/kernel/storage; ./fs_share_target_tc001.sh + +## net +net_link_tc001; cd os_smoke/testcases/kernel/net; ./net_link_tc001.sh +tcp_segmentation_offload_tc001; cd os_smoke/testcases/kernel/net; ./tcp_segmentation_offload_tc001.sh +tcp_iperf_tc001; cd os_smoke/testcases/kernel/net; ./tcp_iperf_tc001.sh +tcp_echo_tc002; cd os_smoke/testcases/kernel/net; ./tcp_echo_tc002.sh +tcp_concurrent_tc003; cd os_smoke/testcases/kernel/net; ./tcp_concurrent_tc003.sh +tcp_tuning_tc004; cd os_smoke/testcases/kernel/net; ./tcp_tuning_tc004.sh +tcp_monitor_tc005; cd os_smoke/testcases/kernel/net; ./tcp_monitor_tc005.sh +udp_iperf_tc001; cd os_smoke/testcases/kernel/net; ./udp_iperf_tc001.sh +udp_echo_tc002; cd os_smoke/testcases/kernel/net; ./udp_echo_tc002.sh +udp_packet_size_tc003; cd os_smoke/testcases/kernel/net; ./udp_packet_size_tc003.sh +udp_lossy_perf_tc004; cd os_smoke/testcases/kernel/net; ./udp_lossy_perf_tc004.sh +udp_monitor_tc005; cd os_smoke/testcases/kernel/net; ./udp_monitor_tc005.sh +ipv4_tc001; cd os_smoke/testcases/kernel/net; ./ipv4_tc001.sh +ipv6_tc001; cd os_smoke/testcases/kernel/net; ./ipv6_tc001.sh +bond_tc001; cd os_smoke/testcases/kernel/net; ./bond_tc001.sh +ip_link_tc001; cd os_smoke/testcases/kernel/net; ./ip_link_tc001.sh +ip_route_tc001; cd os_smoke/testcases/kernel/net; ./ip_route_tc001.sh +nettools_tc001; cd os_smoke/testcases/kernel/net; ./nettools_tc001.sh +ethtool_tc001; cd os_smoke/testcases/kernel/net; ./ethtool_tc001.sh + +## filesystem +ntfs_tc001; cd os_smoke/testcases/kernel/fs; ./ntfs_tc001.sh +ext4_tc001; cd os_smoke/testcases/kernel/fs; ./ext4_tc001.sh +ext3_tc001; cd os_smoke/testcases/kernel/fs; ./ext3_tc001.sh +ext2_tc001; cd os_smoke/testcases/kernel/fs; ./ext2_tc001.sh +fat32_tc001; cd os_smoke/testcases/kernel/fs; ./fat32_tc001.sh +vfat_tc001; cd os_smoke/testcases/kernel/fs; ./vfat_tc001.sh +xfs_tc001; cd os_smoke/testcases/kernel/fs; ./xfs_tc001.sh +longest_filename_tc001; cd os_smoke/testcases/kernel/fs; ./longest_filename_tc001.sh +block_vfs_ext4_tc001; cd os_smoke/testcases/kernel/fs; ./block_vfs_ext4_tc001.sh +vfs_stdin_stdout_tc001; cd os_smoke/testcases/kernel/fs; ./vfs_stdin_stdout_tc001.sh +fs_save_search_tc001; cd os_smoke/testcases/kernel/fs; ./fs_save_search_tc001.sh + +## command - 文件/文本/压缩等命令行工具 +bzip2_tc001; cd os_smoke/testcases/baseos/command; ./bzip2_tc001.sh +cat_tc001; cd os_smoke/testcases/baseos/command; ./cat_tc001.sh +chmod_tc001; cd os_smoke/testcases/baseos/command; ./chmod_tc001.sh +chown_tc001; cd os_smoke/testcases/baseos/command; ./chown_tc001.sh +cp_tc001; cd os_smoke/testcases/baseos/command; ./cp_tc001.sh +cut_tc001; cd os_smoke/testcases/baseos/command; ./cut_tc001.sh +df_tc001; cd os_smoke/testcases/baseos/command; ./df_tc001.sh +du_tc001; cd os_smoke/testcases/baseos/command; ./du_tc001.sh +egrep_tc001; cd os_smoke/testcases/baseos/command; ./egrep_tc001.sh +fs_basic_tools_tc001; cd os_smoke/testcases/baseos/command; ./fs_basic_tools_tc001.sh +grep_tc001; cd os_smoke/testcases/baseos/command; ./grep_tc001.sh +gzip_tc001; cd os_smoke/testcases/baseos/command; ./gzip_tc001.sh +head_tc001; cd os_smoke/testcases/baseos/command; ./head_tc001.sh +less_tc001; cd os_smoke/testcases/baseos/command; ./less_tc001.sh +ln_tc001; cd os_smoke/testcases/baseos/command; ./ln_tc001.sh +ls_tc001; cd os_smoke/testcases/baseos/command; ./ls_tc001.sh +mkdir_tc001; cd os_smoke/testcases/baseos/command; ./mkdir_tc001.sh +more_tc001; cd os_smoke/testcases/baseos/command; ./more_tc001.sh +mv_tc001; cd os_smoke/testcases/baseos/command; ./mv_tc001.sh +nl_tc001; cd os_smoke/testcases/baseos/command; ./nl_tc001.sh +sed_tc001; cd os_smoke/testcases/baseos/command; ./sed_tc001.sh +sort_tc001; cd os_smoke/testcases/baseos/command; ./sort_tc001.sh +stat_tc001; cd os_smoke/testcases/baseos/command; ./stat_tc001.sh +tail_tc001; cd os_smoke/testcases/baseos/command; ./tail_tc001.sh +tar_tc001; cd os_smoke/testcases/baseos/command; ./tar_tc001.sh +text_tools_tc001; cd os_smoke/testcases/baseos/command; ./text_tools_tc001.sh +touch_tc001; cd os_smoke/testcases/baseos/command; ./touch_tc001.sh +tr_tc001; cd os_smoke/testcases/baseos/command; ./tr_tc001.sh +uniq_tc001; cd os_smoke/testcases/baseos/command; ./uniq_tc001.sh +wc_tc001; cd os_smoke/testcases/baseos/command; ./wc_tc001.sh +xz_tc001; cd os_smoke/testcases/baseos/command; ./xz_tc001.sh +zip_tc001; cd os_smoke/testcases/baseos/command; ./zip_tc001.sh +rm_tc001; cd os_smoke/testcases/baseos/command; ./rm_tc001.sh +find_tc001; cd os_smoke/testcases/baseos/command; ./find_tc001.sh +awk_tc001; cd os_smoke/testcases/baseos/command; ./awk_tc001.sh +dd_tc001; cd os_smoke/testcases/baseos/command; ./dd_tc001.sh + +## user - 用户和组管理 +useradd_tc001; cd os_smoke/testcases/baseos/user; ./useradd_tc001.sh +userdel_tc001; cd os_smoke/testcases/baseos/user; ./userdel_tc001.sh +usermod_tc001; cd os_smoke/testcases/baseos/user; ./usermod_tc001.sh +groupadd_tc001; cd os_smoke/testcases/baseos/user; ./groupadd_tc001.sh +groupdel_tc001; cd os_smoke/testcases/baseos/user; ./groupdel_tc001.sh +id_tc001; cd os_smoke/testcases/baseos/user; ./id_tc001.sh +who_tc001; cd os_smoke/testcases/baseos/user; ./who_tc001.sh +whoami_tc001; cd os_smoke/testcases/baseos/user; ./whoami_tc001.sh + +## command - 进程管理工具(从process迁入) +ps_tc001; cd os_smoke/testcases/baseos/command; ./ps_tc001.sh +kill_tc001; cd os_smoke/testcases/baseos/command; ./kill_tc001.sh +pgrep_tc001; cd os_smoke/testcases/baseos/command; ./pgrep_tc001.sh +renice_tc001; cd os_smoke/testcases/baseos/command; ./renice_tc001.sh + +## network - 网络配置与工具 +nic_tool_cfg_tc001; cd os_smoke/testcases/baseos/network; ./nic_tool_cfg_tc001.sh +network_manager_tc001; cd os_smoke/testcases/baseos/network; ./network_manager_tc001.sh +dns_resolve_tc001; cd os_smoke/testcases/baseos/network; ./dns_resolve_tc001.sh +ip_tool_basic_tc001; cd os_smoke/testcases/baseos/network; ./ip_tool_basic_tc001.sh +tcp_listen_tc001; cd os_smoke/testcases/baseos/network; ./tcp_listen_tc001.sh +ping_tc001; cd os_smoke/testcases/baseos/network; ./ping_tc001.sh +netstat_tc001; cd os_smoke/testcases/baseos/network; ./netstat_tc001.sh + +## systools - 系统工具(系统信息/监控/配置) +hostname_tc001; cd os_smoke/testcases/baseos/systools; ./hostname_tc001.sh +hostnamectl_tc001; cd os_smoke/testcases/baseos/systools; ./hostnamectl_tc001.sh +locale_tc001; cd os_smoke/testcases/baseos/systools; ./locale_tc001.sh +localectl_tc001; cd os_smoke/testcases/baseos/systools; ./localectl_tc001.sh +system_info_tc001; cd os_smoke/testcases/baseos/systools; ./system_info_tc001.sh +date_time_tool_tc001; cd os_smoke/testcases/baseos/systools; ./date_time_tool_tc001.sh +cpu_monitor_tool_tc001; cd os_smoke/testcases/baseos/systools; ./cpu_monitor_tool_tc001.sh +mem_monitor_tool_tc002; cd os_smoke/testcases/baseos/systools; ./mem_monitor_tool_tc002.sh +io_monitor_tool_tc003; cd os_smoke/testcases/baseos/systools; ./io_monitor_tool_tc003.sh +net_monitor_tool_tc004; cd os_smoke/testcases/baseos/systools; ./net_monitor_tool_tc004.sh +dracut_tc001; cd os_smoke/testcases/baseos/systools; ./dracut_tc001.sh +tmux_tool_tc001; cd os_smoke/testcases/baseos/systools; ./tmux_tool_tc001.sh + +## service - 系统服务/systemd/日志管理 +sshd_tc001; cd os_smoke/testcases/baseos/service; ./sshd_tc001.sh +dbus_service_tc001; cd os_smoke/testcases/baseos/service; ./dbus_service_tc001.sh +crond_service_tc002; cd os_smoke/testcases/baseos/service; ./crond_service_tc002.sh +chronyd_service_tc003; cd os_smoke/testcases/baseos/service; ./chronyd_service_tc003.sh +polkit_service_tc004; cd os_smoke/testcases/baseos/service; ./polkit_service_tc004.sh +tuned_service_tc005; cd os_smoke/testcases/baseos/service; ./tuned_service_tc005.sh +atd_service_tc006; cd os_smoke/testcases/baseos/service; ./atd_service_tc006.sh +irqbalance_service_tc007; cd os_smoke/testcases/baseos/service; ./irqbalance_service_tc007.sh +postfix_service_tc008; cd os_smoke/testcases/baseos/service; ./postfix_service_tc008.sh +snmp_tool_tc001; cd os_smoke/testcases/baseos/service; ./snmp_tool_tc001.sh +systemd_basic_func_tc001; cd os_smoke/testcases/baseos/service; ./systemd_basic_func_tc001.sh +systemd_custom_service_tc002; cd os_smoke/testcases/baseos/service; ./systemd_custom_service_tc002.sh +systemd_daemon_restart_tc003; cd os_smoke/testcases/baseos/service; ./systemd_daemon_restart_tc003.sh +sysctl_tool_tc001; cd os_smoke/testcases/baseos/service; ./sysctl_tool_tc001.sh +rsyslog_service_tc001; cd os_smoke/testcases/baseos/service; ./rsyslog_service_tc001.sh +journald_query_tc002; cd os_smoke/testcases/baseos/service; ./journald_query_tc002.sh +logrotate_tc003; cd os_smoke/testcases/baseos/service; ./logrotate_tc003.sh +kernel_log_record_tc004; cd os_smoke/testcases/baseos/service; ./kernel_log_record_tc004.sh +log_access_control_tc005; cd os_smoke/testcases/baseos/service; ./log_access_control_tc005.sh + +## security - 安全(访问控制+防火墙+身份鉴别+审计) +lsm_framework_tc001; cd os_smoke/testcases/baseos/security; ./lsm_framework_tc001.sh +dac_access_control_tc002; cd os_smoke/testcases/baseos/security; ./dac_access_control_tc002.sh +acl_access_control_tc003; cd os_smoke/testcases/baseos/security; ./acl_access_control_tc003.sh +audit_service_tc001; cd os_smoke/testcases/baseos/security; ./audit_service_tc001.sh +audit_file_ops_tc002; cd os_smoke/testcases/baseos/security; ./audit_file_ops_tc002.sh +audit_log_verify_tc003; cd os_smoke/testcases/baseos/security; ./audit_log_verify_tc003.sh +audit_identity_mac_tc004; cd os_smoke/testcases/baseos/security; ./audit_identity_mac_tc004.sh +firewalld_service_tc001; cd os_smoke/testcases/baseos/security; ./firewalld_service_tc001.sh +iptables_rules_tc002; cd os_smoke/testcases/baseos/security; ./iptables_rules_tc002.sh +firewall_port_access_tc003; cd os_smoke/testcases/baseos/security; ./firewall_port_access_tc003.sh +firewalld_zone_manage_tc004; cd os_smoke/testcases/baseos/security; ./firewalld_zone_manage_tc004.sh +user_uid_unique_tc001; cd os_smoke/testcases/baseos/security; ./user_uid_unique_tc001.sh + + +## compiler - 编译器 +gcc_tc001; cd os_smoke/testcases/develop/compiler; ./gcc_tc001.sh +gxx_tc001; cd os_smoke/testcases/develop/compiler; ./gxx_tc001.sh +clang_tc001; cd os_smoke/testcases/develop/compiler; ./clang_tc001.sh + +## language - 编程语言运行时 +java_tc001; cd os_smoke/testcases/develop/language; ./java_tc001.sh +python_tc001; cd os_smoke/testcases/develop/language; ./python_tc001.sh +golang_tc001; cd os_smoke/testcases/develop/language; ./golang_tc001.sh +rust_tc001; cd os_smoke/testcases/develop/language; ./rust_tc001.sh +ruby_tc001; cd os_smoke/testcases/develop/language; ./ruby_tc001.sh +perl_tc001; cd os_smoke/testcases/develop/language; ./perl_tc001.sh +nodejs_tc001; cd os_smoke/testcases/develop/language; ./nodejs_tc001.sh + +## buildtool - 构建工具/版本管理 +make_tc001; cd os_smoke/testcases/develop/buildtool; ./make_tc001.sh +cmake_tc001; cd os_smoke/testcases/develop/buildtool; ./cmake_tc001.sh +binutils_tc001; cd os_smoke/testcases/develop/buildtool; ./binutils_tc001.sh +autoconf_tc001; cd os_smoke/testcases/develop/buildtool; ./autoconf_tc001.sh +rpmbuild_tc001; cd os_smoke/testcases/develop/buildtool; ./rpmbuild_tc001.sh +git_tc001; cd os_smoke/testcases/develop/buildtool; ./git_tc001.sh + +## debug - 调试/分析工具 +gdb_tc001; cd os_smoke/testcases/develop/debug; ./gdb_tc001.sh +strace_tc001; cd os_smoke/testcases/develop/debug; ./strace_tc001.sh +valgrind_tc001; cd os_smoke/testcases/develop/debug; ./valgrind_tc001.sh +perf_tc001; cd os_smoke/testcases/develop/debug; ./perf_tc001.sh + +## editor - 文本编辑器 +vim_tc001; cd os_smoke/testcases/develop/editor; ./vim_tc001.sh +emacs_tc001; cd os_smoke/testcases/develop/editor; ./emacs_tc001.sh +nano_tc001; cd os_smoke/testcases/develop/editor; ./nano_tc001.sh + +## graphlib - 图形库 +cairo_tc001; cd os_smoke/testcases/develop/graphlib; ./cairo_tc001.sh +gtk_tc001; cd os_smoke/testcases/develop/graphlib; ./gtk_tc001.sh +opengl_tc001; cd os_smoke/testcases/develop/graphlib; ./opengl_tc001.sh +qt_tc001; cd os_smoke/testcases/develop/graphlib; ./qt_tc001.sh + diff --git a/baseline/cases_app b/baseline/cases_app new file mode 100755 index 0000000..ce50c7f --- /dev/null +++ b/baseline/cases_app @@ -0,0 +1,4 @@ +# app 应用程序类测试用例 +# 格式: 用例名; cd os_smoke/testcases/app/子目录; ./用例脚本 +# 示例: +# nginx_tc001; cd os_smoke/testcases/app/nginx; ./nginx_tc001.sh diff --git a/baseline/cases_baseos b/baseline/cases_baseos new file mode 100755 index 0000000..49c4fa1 --- /dev/null +++ b/baseline/cases_baseos @@ -0,0 +1,108 @@ +## command - 文件/文本/压缩等命令行工具 +bzip2_tc001; cd os_smoke/testcases/baseos/command; ./bzip2_tc001.sh +cat_tc001; cd os_smoke/testcases/baseos/command; ./cat_tc001.sh +chmod_tc001; cd os_smoke/testcases/baseos/command; ./chmod_tc001.sh +chown_tc001; cd os_smoke/testcases/baseos/command; ./chown_tc001.sh +cp_tc001; cd os_smoke/testcases/baseos/command; ./cp_tc001.sh +cut_tc001; cd os_smoke/testcases/baseos/command; ./cut_tc001.sh +df_tc001; cd os_smoke/testcases/baseos/command; ./df_tc001.sh +du_tc001; cd os_smoke/testcases/baseos/command; ./du_tc001.sh +egrep_tc001; cd os_smoke/testcases/baseos/command; ./egrep_tc001.sh +fs_basic_tools_tc001; cd os_smoke/testcases/baseos/command; ./fs_basic_tools_tc001.sh +grep_tc001; cd os_smoke/testcases/baseos/command; ./grep_tc001.sh +gzip_tc001; cd os_smoke/testcases/baseos/command; ./gzip_tc001.sh +head_tc001; cd os_smoke/testcases/baseos/command; ./head_tc001.sh +less_tc001; cd os_smoke/testcases/baseos/command; ./less_tc001.sh +ln_tc001; cd os_smoke/testcases/baseos/command; ./ln_tc001.sh +ls_tc001; cd os_smoke/testcases/baseos/command; ./ls_tc001.sh +mkdir_tc001; cd os_smoke/testcases/baseos/command; ./mkdir_tc001.sh +more_tc001; cd os_smoke/testcases/baseos/command; ./more_tc001.sh +mv_tc001; cd os_smoke/testcases/baseos/command; ./mv_tc001.sh +nl_tc001; cd os_smoke/testcases/baseos/command; ./nl_tc001.sh +sed_tc001; cd os_smoke/testcases/baseos/command; ./sed_tc001.sh +sort_tc001; cd os_smoke/testcases/baseos/command; ./sort_tc001.sh +stat_tc001; cd os_smoke/testcases/baseos/command; ./stat_tc001.sh +tail_tc001; cd os_smoke/testcases/baseos/command; ./tail_tc001.sh +tar_tc001; cd os_smoke/testcases/baseos/command; ./tar_tc001.sh +text_tools_tc001; cd os_smoke/testcases/baseos/command; ./text_tools_tc001.sh +touch_tc001; cd os_smoke/testcases/baseos/command; ./touch_tc001.sh +tr_tc001; cd os_smoke/testcases/baseos/command; ./tr_tc001.sh +uniq_tc001; cd os_smoke/testcases/baseos/command; ./uniq_tc001.sh +wc_tc001; cd os_smoke/testcases/baseos/command; ./wc_tc001.sh +xz_tc001; cd os_smoke/testcases/baseos/command; ./xz_tc001.sh +zip_tc001; cd os_smoke/testcases/baseos/command; ./zip_tc001.sh + +## user - 用户和组管理 +useradd_tc001; cd os_smoke/testcases/baseos/user; ./useradd_tc001.sh +userdel_tc001; cd os_smoke/testcases/baseos/user; ./userdel_tc001.sh +usermod_tc001; cd os_smoke/testcases/baseos/user; ./usermod_tc001.sh +groupadd_tc001; cd os_smoke/testcases/baseos/user; ./groupadd_tc001.sh +groupdel_tc001; cd os_smoke/testcases/baseos/user; ./groupdel_tc001.sh +id_tc001; cd os_smoke/testcases/baseos/user; ./id_tc001.sh +who_tc001; cd os_smoke/testcases/baseos/user; ./who_tc001.sh +whoami_tc001; cd os_smoke/testcases/baseos/user; ./whoami_tc001.sh + +## command - 进程管理工具(从process迁入) +ps_tc001; cd os_smoke/testcases/baseos/command; ./ps_tc001.sh +kill_tc001; cd os_smoke/testcases/baseos/command; ./kill_tc001.sh +pgrep_tc001; cd os_smoke/testcases/baseos/command; ./pgrep_tc001.sh +renice_tc001; cd os_smoke/testcases/baseos/command; ./renice_tc001.sh + +## network - 网络配置与工具 +nic_tool_cfg_tc001; cd os_smoke/testcases/baseos/network; ./nic_tool_cfg_tc001.sh +network_manager_tc001; cd os_smoke/testcases/baseos/network; ./network_manager_tc001.sh +dns_resolve_tc001; cd os_smoke/testcases/baseos/network; ./dns_resolve_tc001.sh +ip_tool_basic_tc001; cd os_smoke/testcases/baseos/network; ./ip_tool_basic_tc001.sh +tcp_listen_tc001; cd os_smoke/testcases/baseos/network; ./tcp_listen_tc001.sh +ping_tc001; cd os_smoke/testcases/baseos/network; ./ping_tc001.sh +netstat_tc001; cd os_smoke/testcases/baseos/network; ./netstat_tc001.sh + +## systools - 系统工具(系统信息/监控/配置) +system_info_tc001; cd os_smoke/testcases/baseos/systools; ./system_info_tc001.sh +hostname_tc001; cd os_smoke/testcases/baseos/systools; ./hostname_tc001.sh +hostnamectl_tc001; cd os_smoke/testcases/baseos/systools; ./hostnamectl_tc001.sh +locale_tc001; cd os_smoke/testcases/baseos/systools; ./locale_tc001.sh +localectl_tc001; cd os_smoke/testcases/baseos/systools; ./localectl_tc001.sh +date_time_tool_tc001; cd os_smoke/testcases/baseos/systools; ./date_time_tool_tc001.sh +cpu_monitor_tool_tc001; cd os_smoke/testcases/baseos/systools; ./cpu_monitor_tool_tc001.sh +mem_monitor_tool_tc002; cd os_smoke/testcases/baseos/systools; ./mem_monitor_tool_tc002.sh +io_monitor_tool_tc003; cd os_smoke/testcases/baseos/systools; ./io_monitor_tool_tc003.sh +net_monitor_tool_tc004; cd os_smoke/testcases/baseos/systools; ./net_monitor_tool_tc004.sh +dracut_tc001; cd os_smoke/testcases/baseos/systools; ./dracut_tc001.sh +tmux_tool_tc001; cd os_smoke/testcases/baseos/systools; ./tmux_tool_tc001.sh + +## service - 系统服务/systemd/日志管理 +sshd_tc001; cd os_smoke/testcases/baseos/service; ./sshd_tc001.sh +dbus_service_tc001; cd os_smoke/testcases/baseos/service; ./dbus_service_tc001.sh +crond_service_tc002; cd os_smoke/testcases/baseos/service; ./crond_service_tc002.sh +chronyd_service_tc003; cd os_smoke/testcases/baseos/service; ./chronyd_service_tc003.sh +polkit_service_tc004; cd os_smoke/testcases/baseos/service; ./polkit_service_tc004.sh +tuned_service_tc005; cd os_smoke/testcases/baseos/service; ./tuned_service_tc005.sh +atd_service_tc006; cd os_smoke/testcases/baseos/service; ./atd_service_tc006.sh +irqbalance_service_tc007; cd os_smoke/testcases/baseos/service; ./irqbalance_service_tc007.sh +postfix_service_tc008; cd os_smoke/testcases/baseos/service; ./postfix_service_tc008.sh +snmp_tool_tc001; cd os_smoke/testcases/baseos/service; ./snmp_tool_tc001.sh +systemd_basic_func_tc001; cd os_smoke/testcases/baseos/service; ./systemd_basic_func_tc001.sh +systemd_custom_service_tc002; cd os_smoke/testcases/baseos/service; ./systemd_custom_service_tc002.sh +systemd_daemon_restart_tc003; cd os_smoke/testcases/baseos/service; ./systemd_daemon_restart_tc003.sh +sysctl_tool_tc001; cd os_smoke/testcases/baseos/service; ./sysctl_tool_tc001.sh +rsyslog_service_tc001; cd os_smoke/testcases/baseos/service; ./rsyslog_service_tc001.sh +journald_query_tc002; cd os_smoke/testcases/baseos/service; ./journald_query_tc002.sh +logrotate_tc003; cd os_smoke/testcases/baseos/service; ./logrotate_tc003.sh +kernel_log_record_tc004; cd os_smoke/testcases/baseos/service; ./kernel_log_record_tc004.sh +log_access_control_tc005; cd os_smoke/testcases/baseos/service; ./log_access_control_tc005.sh + +## security - 安全(访问控制+防火墙+身份鉴别+审计) +lsm_framework_tc001; cd os_smoke/testcases/baseos/security; ./lsm_framework_tc001.sh +dac_access_control_tc002; cd os_smoke/testcases/baseos/security; ./dac_access_control_tc002.sh +acl_access_control_tc003; cd os_smoke/testcases/baseos/security; ./acl_access_control_tc003.sh +audit_service_tc001; cd os_smoke/testcases/baseos/security; ./audit_service_tc001.sh +audit_file_ops_tc002; cd os_smoke/testcases/baseos/security; ./audit_file_ops_tc002.sh +audit_log_verify_tc003; cd os_smoke/testcases/baseos/security; ./audit_log_verify_tc003.sh +audit_identity_mac_tc004; cd os_smoke/testcases/baseos/security; ./audit_identity_mac_tc004.sh +firewalld_service_tc001; cd os_smoke/testcases/baseos/security; ./firewalld_service_tc001.sh +iptables_rules_tc002; cd os_smoke/testcases/baseos/security; ./iptables_rules_tc002.sh +firewall_port_access_tc003; cd os_smoke/testcases/baseos/security; ./firewall_port_access_tc003.sh +firewalld_zone_manage_tc004; cd os_smoke/testcases/baseos/security; ./firewalld_zone_manage_tc004.sh +user_uid_unique_tc001; cd os_smoke/testcases/baseos/security; ./user_uid_unique_tc001.sh + diff --git a/baseline/cases_develop b/baseline/cases_develop new file mode 100755 index 0000000..48a7afb --- /dev/null +++ b/baseline/cases_develop @@ -0,0 +1,38 @@ +## compiler - 编译器 +gcc_tc001; cd os_smoke/testcases/develop/compiler; ./gcc_tc001.sh +gxx_tc001; cd os_smoke/testcases/develop/compiler; ./gxx_tc001.sh +clang_tc001; cd os_smoke/testcases/develop/compiler; ./clang_tc001.sh + +## language - 编程语言运行时 +java_tc001; cd os_smoke/testcases/develop/language; ./java_tc001.sh +python_tc001; cd os_smoke/testcases/develop/language; ./python_tc001.sh +golang_tc001; cd os_smoke/testcases/develop/language; ./golang_tc001.sh +rust_tc001; cd os_smoke/testcases/develop/language; ./rust_tc001.sh +ruby_tc001; cd os_smoke/testcases/develop/language; ./ruby_tc001.sh +perl_tc001; cd os_smoke/testcases/develop/language; ./perl_tc001.sh +nodejs_tc001; cd os_smoke/testcases/develop/language; ./nodejs_tc001.sh + +## buildtool - 构建工具/版本管理 +make_tc001; cd os_smoke/testcases/develop/buildtool; ./make_tc001.sh +cmake_tc001; cd os_smoke/testcases/develop/buildtool; ./cmake_tc001.sh +binutils_tc001; cd os_smoke/testcases/develop/buildtool; ./binutils_tc001.sh +autoconf_tc001; cd os_smoke/testcases/develop/buildtool; ./autoconf_tc001.sh +rpmbuild_tc001; cd os_smoke/testcases/develop/buildtool; ./rpmbuild_tc001.sh +git_tc001; cd os_smoke/testcases/develop/buildtool; ./git_tc001.sh + +## debug - 调试/分析工具 +gdb_tc001; cd os_smoke/testcases/develop/debug; ./gdb_tc001.sh +strace_tc001; cd os_smoke/testcases/develop/debug; ./strace_tc001.sh +valgrind_tc001; cd os_smoke/testcases/develop/debug; ./valgrind_tc001.sh +perf_tc001; cd os_smoke/testcases/develop/debug; ./perf_tc001.sh + +## editor - 文本编辑器 +vim_tc001; cd os_smoke/testcases/develop/editor; ./vim_tc001.sh +emacs_tc001; cd os_smoke/testcases/develop/editor; ./emacs_tc001.sh +nano_tc001; cd os_smoke/testcases/develop/editor; ./nano_tc001.sh + +## graphlib - 图形库 +cairo_tc001; cd os_smoke/testcases/develop/graphlib; ./cairo_tc001.sh +gtk_tc001; cd os_smoke/testcases/develop/graphlib; ./gtk_tc001.sh +opengl_tc001; cd os_smoke/testcases/develop/graphlib; ./opengl_tc001.sh +qt_tc001; cd os_smoke/testcases/develop/graphlib; ./qt_tc001.sh diff --git a/baseline/cases_driver b/baseline/cases_driver new file mode 100644 index 0000000..e649e20 --- /dev/null +++ b/baseline/cases_driver @@ -0,0 +1,4 @@ +# driver 第三方驱动测试用例 +# 格式: 用例名; cd os_smoke/testcases/driver/子目录; ./用例脚本 +# 示例: +# nvme_driver_tc001; cd os_smoke/testcases/driver/nvme; ./nvme_driver_tc001.sh diff --git a/baseline/cases_kernel b/baseline/cases_kernel new file mode 100755 index 0000000..d3a50c3 --- /dev/null +++ b/baseline/cases_kernel @@ -0,0 +1,66 @@ +## cpu func +taskset_process_cpu_tc001; cd os_smoke/testcases/kernel/cpu; ./taskset_process_cpu_tc001.sh +taskset_process_cpus_tc001; cd os_smoke/testcases/kernel/cpu; ./taskset_process_cpus_tc001.sh +numa_access_tc001; cd os_smoke/testcases/kernel/cpu; ./numa_access_tc001.sh +numa_access_cpu_tc001; cd os_smoke/testcases/kernel/cpu; ./numa_access_cpu_tc001.sh +hw_rand_tc001; cd os_smoke/testcases/kernel/cpu; ./hw_rand_tc001.sh +mutex_protect_tc001; cd os_smoke/testcases/kernel/cpu; ./mutex_protect_tc001.sh +soft_cryp_tc001; cd os_smoke/testcases/kernel/cpu; ./soft_cryp_tc001.sh + +## cpu sched +auto_load_balance_tc001; cd os_smoke/testcases/kernel/sched; ./auto_load_balance_tc001.sh +process_load_balance_tc001; cd os_smoke/testcases/kernel/sched; ./process_load_balance_tc001.sh +cpu_sched_numabind_tc001; cd os_smoke/testcases/kernel/sched; ./cpu_sched_numabind_tc001.sh +process_renice_tc001; cd os_smoke/testcases/kernel/sched; ./process_renice_tc001.sh +process_rt_tc001; cd os_smoke/testcases/kernel/sched; ./process_rt_tc001.sh +task_status_check_tc001; cd os_smoke/testcases/kernel/sched; ./task_status_check_tc001.sh + +## mem +huge_page_tc001; cd os_smoke/testcases/kernel/mem; ./huge_page_tc001.sh +numa_aware_tc001; cd os_smoke/testcases/kernel/mem; ./numa_aware_tc001.sh + +## storage +soft_raid0_tc001; cd os_smoke/testcases/kernel/storage; ./soft_raid0_tc001.sh +soft_raid1_tc001; cd os_smoke/testcases/kernel/storage; ./soft_raid1_tc001.sh +soft_raid5_tc001; cd os_smoke/testcases/kernel/storage; ./soft_raid5_tc001.sh +soft_raid6_tc001; cd os_smoke/testcases/kernel/storage; ./soft_raid6_tc001.sh +soft_raid10_tc001; cd os_smoke/testcases/kernel/storage; ./soft_raid10_tc001.sh +swap_tc001; cd os_smoke/testcases/kernel/storage; ./swap_tc001.sh +slow_device_caches_tc001; cd os_smoke/testcases/kernel/storage; ./slow_device_caches_tc001.sh +nvme_swap_tc001; cd os_smoke/testcases/kernel/storage; ./nvme_swap_tc001.sh +lvm_adjust_partition_tc001; cd os_smoke/testcases/kernel/storage; ./lvm_adjust_partition_tc001.sh +fs_share_target_tc001; cd os_smoke/testcases/kernel/storage; ./fs_share_target_tc001.sh + +## net +net_link_tc001; cd os_smoke/testcases/kernel/net; ./net_link_tc001.sh +tcp_segmentation_offload_tc001; cd os_smoke/testcases/kernel/net; ./tcp_segmentation_offload_tc001.sh +tcp_iperf_tc001; cd os_smoke/testcases/kernel/net; ./tcp_iperf_tc001.sh +tcp_echo_tc002; cd os_smoke/testcases/kernel/net; ./tcp_echo_tc002.sh +tcp_concurrent_tc003; cd os_smoke/testcases/kernel/net; ./tcp_concurrent_tc003.sh +tcp_tuning_tc004; cd os_smoke/testcases/kernel/net; ./tcp_tuning_tc004.sh +tcp_monitor_tc005; cd os_smoke/testcases/kernel/net; ./tcp_monitor_tc005.sh +udp_iperf_tc001; cd os_smoke/testcases/kernel/net; ./udp_iperf_tc001.sh +udp_echo_tc002; cd os_smoke/testcases/kernel/net; ./udp_echo_tc002.sh +udp_packet_size_tc003; cd os_smoke/testcases/kernel/net; ./udp_packet_size_tc003.sh +udp_lossy_perf_tc004; cd os_smoke/testcases/kernel/net; ./udp_lossy_perf_tc004.sh +udp_monitor_tc005; cd os_smoke/testcases/kernel/net; ./udp_monitor_tc005.sh +ipv4_tc001; cd os_smoke/testcases/kernel/net; ./ipv4_tc001.sh +ipv6_tc001; cd os_smoke/testcases/kernel/net; ./ipv6_tc001.sh +bond_tc001; cd os_smoke/testcases/kernel/net; ./bond_tc001.sh +ip_link_tc001; cd os_smoke/testcases/kernel/net; ./ip_link_tc001.sh +ip_route_tc001; cd os_smoke/testcases/kernel/net; ./ip_route_tc001.sh +nettools_tc001; cd os_smoke/testcases/kernel/net; ./nettools_tc001.sh +ethtool_tc001; cd os_smoke/testcases/kernel/net; ./ethtool_tc001.sh + +## filesystem +ntfs_tc001; cd os_smoke/testcases/kernel/fs; ./ntfs_tc001.sh +ext4_tc001; cd os_smoke/testcases/kernel/fs; ./ext4_tc001.sh +ext3_tc001; cd os_smoke/testcases/kernel/fs; ./ext3_tc001.sh +ext2_tc001; cd os_smoke/testcases/kernel/fs; ./ext2_tc001.sh +fat32_tc001; cd os_smoke/testcases/kernel/fs; ./fat32_tc001.sh +vfat_tc001; cd os_smoke/testcases/kernel/fs; ./vfat_tc001.sh +xfs_tc001; cd os_smoke/testcases/kernel/fs; ./xfs_tc001.sh +longest_filename_tc001; cd os_smoke/testcases/kernel/fs; ./longest_filename_tc001.sh +block_vfs_ext4_tc001; cd os_smoke/testcases/kernel/fs; ./block_vfs_ext4_tc001.sh +vfs_stdin_stdout_tc001; cd os_smoke/testcases/kernel/fs; ./vfs_stdin_stdout_tc001.sh +fs_save_search_tc001; cd os_smoke/testcases/kernel/fs; ./fs_save_search_tc001.sh diff --git a/control b/control new file mode 100755 index 0000000..2d6bece --- /dev/null +++ b/control @@ -0,0 +1,14 @@ +#测试套名称 +CONTROL_SUITE_NAME='os_smoke' + +#测试套依赖软件包 +#注意:仅保留编译依赖和广泛使用的通用包,其余包由对应用例脚本自行通过install_packages/remove_packages管理 +CONTROL_PRE_PKG='gcc expect kernel-devel kernel-headers openssl-devel numactl-libs numactl-devel net-tools util-linux stress-ng procps-ng cairo-devel' + +#测试套编译安装命令 +CONTROL_COMPILE_CMD='make clean; make ; make install' + +#测试套clean命令 +CONTROL_CLEAN_CMD='make clean' + + diff --git a/os_smoke_src/Makefile b/os_smoke_src/Makefile new file mode 100755 index 0000000..12e8c3b --- /dev/null +++ b/os_smoke_src/Makefile @@ -0,0 +1,22 @@ +subdir := $(shell find . -maxdepth 1 -type d) +dirs := $(filter-out .,$(subdir)) +dirs := $(basename $(patsubst ./%,%,$(dirs))) + +.PHONY: $(dirs) + +$(dirs): + echo $(dirs) + @for dir in $(dirs); do \ + $(MAKE) -C $$dir || exit "$$?"; \ + done + +install: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir install || exit "$$?"; \ + done + +clean: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir clean || exit "$$?"; \ + done + diff --git a/os_smoke_src/lib/Makefile b/os_smoke_src/lib/Makefile new file mode 100755 index 0000000..96acdbb --- /dev/null +++ b/os_smoke_src/lib/Makefile @@ -0,0 +1,11 @@ +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +default: + @echo "in lib Makefile, no compilation needed" + +install: + mkdir -p $(INSTALL_DIR) + cp -avf common_libs.sh $(INSTALL_DIR)/ + +clean: + @echo "in lib Makefile, nothing to clean" diff --git a/os_smoke_src/lib/common_libs.sh b/os_smoke_src/lib/common_libs.sh new file mode 100755 index 0000000..59015cd --- /dev/null +++ b/os_smoke_src/lib/common_libs.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +#**************************************************# +# 公共库函数 (新框架版本) +# 提供测试用例通用的功能函数 +# 依赖: case_lib.sh (需先 source case_lib.sh) +#**************************************************# + +# 防止重复source +if [ -n "$_COMMON_LIBS_SH_LOADED" ]; then + return 0 2>/dev/null || true +fi +_COMMON_LIBS_SH_LOADED=1 + +#**************************************************# +# 包管理函数 +# 参数1: 操作类型 (install/remove) +# 参数2: 包名列表 (多个包用空格分隔,需用引号包围) +# 返回值: 0-成功, 1-失败 +# 使用示例: +# manage_packages "install" "audit sysstat" +# manage_packages "remove" "audit sysstat" +#**************************************************# +manage_packages() { + local action="$1" + local packages="$2" + + if [ -z "$action" ] || [ -z "$packages" ]; then + tst_res $TFAIL "manage_packages: 参数不能为空, 用法: manage_packages \"pkg1 pkg2\"" + return 1 + fi + + if [ "$action" != "install" ] && [ "$action" != "remove" ]; then + tst_res $TFAIL "manage_packages: 不支持的操作类型: $action (仅支持 install/remove)" + return 1 + fi + + local pkg_cmd="" + if [ "$action" = "install" ]; then + pkg_cmd="yum install -y" + else + pkg_cmd="yum remove -y" + fi + + tst_res $TINFO "manage_packages: ${action} $packages" + + $pkg_cmd $packages >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "manage_packages: $pkg_cmd $packages 执行失败" + return 1 + fi + + # 验证结果 + local pkg_list=($packages) + local fail=0 + + for pkg in "${pkg_list[@]}"; do + if [ "$action" = "install" ]; then + if ! rpm -q "$pkg" >/dev/null 2>&1 && \ + ! rpm -q --whatprovides "$pkg" >/dev/null 2>&1; then + tst_res $TFAIL "manage_packages: $pkg 安装后验证失败" + fail=1 + fi + else + if rpm -q "$pkg" >/dev/null 2>&1; then + tst_res $TFAIL "manage_packages: $pkg 卸载后仍存在" + fail=1 + fi + fi + done + + if [ $fail -ne 0 ]; then + return 1 + fi + + tst_res $TINFO "manage_packages: ${action} $packages 成功" + return 0 +} + +#**************************************************# +# 安装包的便捷函数 +# 参数: 包名列表 (多个包用空格分隔,需用引号包围) +# 使用示例: install_packages "audit sysstat stress-ng" +#**************************************************# +install_packages() { + manage_packages "install" "$1" +} + +#**************************************************# +# 卸载包的便捷函数 +# 参数: 包名列表 (多个包用空格分隔,需用引号包围) +# 使用示例: remove_packages "audit sysstat stress-ng" +#**************************************************# +remove_packages() { + manage_packages "remove" "$1" +} + +#**************************************************# +# 确保命令可用,不存在则安装对应包 +# 参数1: 命令名 (或命令完整路径) +# 参数2: 包名 (可选,不指定则与命令名相同) +# 返回值: 0-命令可用, 1-安装失败 +# 使用示例: +# ensure_command "ethtool" +# ensure_command "/usr/sbin/mdadm" "mdadm" +# ensure_command "mkfs.xfs" "xfsprogs" +# ensure_command "iperf3" +#**************************************************# +ensure_command() { + local cmd="$1" + local pkg="${2:-$(basename $cmd)}" + + if which "$cmd" >/dev/null 2>&1 || [ -x "$cmd" ]; then + return 0 + fi + + tst_res $TINFO "ensure_command: $cmd 不存在, 安装 $pkg" + install_packages "$pkg" +} diff --git a/os_smoke_src/testcases/Makefile b/os_smoke_src/testcases/Makefile new file mode 100755 index 0000000..12e8c3b --- /dev/null +++ b/os_smoke_src/testcases/Makefile @@ -0,0 +1,22 @@ +subdir := $(shell find . -maxdepth 1 -type d) +dirs := $(filter-out .,$(subdir)) +dirs := $(basename $(patsubst ./%,%,$(dirs))) + +.PHONY: $(dirs) + +$(dirs): + echo $(dirs) + @for dir in $(dirs); do \ + $(MAKE) -C $$dir || exit "$$?"; \ + done + +install: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir install || exit "$$?"; \ + done + +clean: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir clean || exit "$$?"; \ + done + diff --git a/os_smoke_src/testcases/app/Makefile b/os_smoke_src/testcases/app/Makefile new file mode 100755 index 0000000..12e8c3b --- /dev/null +++ b/os_smoke_src/testcases/app/Makefile @@ -0,0 +1,22 @@ +subdir := $(shell find . -maxdepth 1 -type d) +dirs := $(filter-out .,$(subdir)) +dirs := $(basename $(patsubst ./%,%,$(dirs))) + +.PHONY: $(dirs) + +$(dirs): + echo $(dirs) + @for dir in $(dirs); do \ + $(MAKE) -C $$dir || exit "$$?"; \ + done + +install: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir install || exit "$$?"; \ + done + +clean: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir clean || exit "$$?"; \ + done + diff --git a/os_smoke_src/testcases/baseos/Makefile b/os_smoke_src/testcases/baseos/Makefile new file mode 100755 index 0000000..12e8c3b --- /dev/null +++ b/os_smoke_src/testcases/baseos/Makefile @@ -0,0 +1,22 @@ +subdir := $(shell find . -maxdepth 1 -type d) +dirs := $(filter-out .,$(subdir)) +dirs := $(basename $(patsubst ./%,%,$(dirs))) + +.PHONY: $(dirs) + +$(dirs): + echo $(dirs) + @for dir in $(dirs); do \ + $(MAKE) -C $$dir || exit "$$?"; \ + done + +install: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir install || exit "$$?"; \ + done + +clean: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir clean || exit "$$?"; \ + done + diff --git a/os_smoke_src/testcases/baseos/command/Makefile b/os_smoke_src/testcases/baseos/command/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/baseos/command/awk_tc001.sh b/os_smoke_src/testcases/baseos/command/awk_tc001.sh new file mode 100755 index 0000000..228530d --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/awk_tc001.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: awk_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: 验证awk命令字段提取、模式匹配和内置变量的基础功能 +# 前置条件: 系统已安装awk +# 用例步骤: +# 1. 使用awk提取指定字段,验证输出正确 +# 2. 使用awk模式匹配过滤行 +# 3. 使用awk内置变量NR/NF +# 预期结果: +# 1. 字段提取正确 +# 2. 模式匹配结果正确 +# 3. NR/NF值正确 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证awk命令字段提取、模式匹配和内置变量的基础功能" + +tcnt=3 +TEST_FILE="/tmp/ts_cmd_awk_tc001.txt" + +setup() +{ + ensure_command "awk" + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +alice 90 math +bob 85 english +carol 95 math +dave 70 english +EOF +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 awk 字段提取" + local out + out=$(awk '{print $1}' "${TEST_FILE}" 2>/dev/null | tr '\n' ' ' | sed 's/ $//') + if [ "${out}" = "alice bob carol dave" ]; then + tst_res $TPASS "awk 字段提取正确" + else + tst_res $TFAIL "awk 字段提取错误: [${out}]" + fi + ;; + 1) + tst_res $TINFO "测试 awk 模式匹配" + local cnt + cnt=$(awk '/math/{print}' "${TEST_FILE}" 2>/dev/null | wc -l) + if [ "${cnt}" -eq 2 ]; then + tst_res $TPASS "awk 模式匹配找到 2 行" + else + tst_res $TFAIL "awk 模式匹配期望 2 行, 实际 ${cnt}" + fi + ;; + 2) + tst_res $TINFO "测试 awk NR/NF 内置变量" + local last_nr last_nf + last_nr=$(awk 'END{print NR}' "${TEST_FILE}" 2>/dev/null) + last_nf=$(awk 'NR==1{print NF}' "${TEST_FILE}" 2>/dev/null) + if [ "${last_nr}" = "4" ] && [ "${last_nf}" = "3" ]; then + tst_res $TPASS "NR=4, NF=3 正确" + else + tst_res $TFAIL "NR=${last_nr}, NF=${last_nf}, 期望 NR=4, NF=3" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/bzip2_tc001.sh b/os_smoke_src/testcases/baseos/command/bzip2_tc001.sh new file mode 100755 index 0000000..596a96f --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/bzip2_tc001.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: bzip2_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证bzip2/bunzip2对文本文件的压缩与解压功能 +# 前置条件: 系统已安装bzip2与bunzip2 +# 用例步骤: +# 1. 使用bzip2 -k压缩测试文件,验证.bz2文件生成且非空 +# 2. 使用bunzip2 -k -c解压到新文件,验证文件非空 +# 3. 比对原始文件与解压后文件内容是否一致 +# 预期结果: +# 1. 压缩成功 +# 2. 解压成功 +# 3. 内容完全一致 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证bzip2/bunzip2对文本文件的压缩与解压功能" + +tcnt=3 +TEST_DIR="/tmp/ts_bzip2_tc001" +TEST_FILE="${TEST_DIR}/test.txt" +BZ2_FILE="${TEST_FILE}.bz2" +UNBZ_FILE="${TEST_DIR}/test_unbz.txt" + +setup() +{ + ensure_command "bzip2" + rm -rf "${TEST_DIR}" + mkdir -p "${TEST_DIR}" || tst_brk $TFAIL "创建 ${TEST_DIR} 失败" + echo -e "line1\nline2\nline3" > "${TEST_FILE}" || tst_brk $TFAIL "创建测试文件失败" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 bzip2 压缩" + bzip2 -k "${TEST_FILE}" 2>/dev/null + if [ -s "${BZ2_FILE}" ]; then + tst_res $TPASS "bzip2 压缩成功, .bz2 文件存在且非空" + else + tst_res $TFAIL "bzip2 压缩失败, ${BZ2_FILE} 不存在或为空" + fi + ;; + 1) + tst_res $TINFO "测试 bunzip2 解压" + bunzip2 -k -c "${BZ2_FILE}" > "${UNBZ_FILE}" 2>/dev/null + if [ -s "${UNBZ_FILE}" ]; then + tst_res $TPASS "bunzip2 解压成功" + else + tst_res $TFAIL "bunzip2 解压失败, ${UNBZ_FILE} 不存在或为空" + fi + ;; + 2) + tst_res $TINFO "比较原始文件与解压文件内容" + if cmp "${TEST_FILE}" "${UNBZ_FILE}" >/dev/null 2>&1; then + tst_res $TPASS "原始文件与解压文件内容一致" + else + tst_res $TFAIL "原始文件与解压文件内容不一致" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/cat_tc001.sh b/os_smoke_src/testcases/baseos/command/cat_tc001.sh new file mode 100755 index 0000000..666cfee --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/cat_tc001.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cat_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证cat命令读取文件内容并输出到标准输出的基础功能 +# 前置条件: /tmp目录可写 +# 用例步骤: +# 1. 创建包含已知内容的测试文件 +# 2. 使用cat读取该文件,验证输出内容与预期完全一致 +# 预期结果: +# 1. cat命令执行成功,返回码为0 +# 2. cat输出内容与预设内容完全一致 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证cat命令读取文件内容并输出到标准输出的基础功能" + +tcnt=2 +TEST_FILE="/tmp/ts_cmd_cat_tc001.txt" +EXPECTED="line1 +line2 +line3" + +setup() +{ + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +line1 +line2 +line3 +EOF + if [ ! -f "${TEST_FILE}" ]; then + tst_brk $TFAIL "创建测试文件失败" + fi +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 cat 读取文件返回码" + cat "${TEST_FILE}" >/dev/null 2>&1 + if [ $? -eq 0 ]; then + tst_res $TPASS "cat 命令执行成功" + else + tst_res $TFAIL "cat 命令执行失败" + fi + ;; + 1) + tst_res $TINFO "测试 cat 输出内容与预期一致" + local out + out=$(cat "${TEST_FILE}" 2>/dev/null) + if [ "${out}" = "${EXPECTED}" ]; then + tst_res $TPASS "cat 输出内容与预期一致" + else + tst_res $TFAIL "cat 输出内容不一致, 期望: [${EXPECTED}], 实际: [${out}]" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/chmod_tc001.sh b/os_smoke_src/testcases/baseos/command/chmod_tc001.sh new file mode 100755 index 0000000..eaf0a2b --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/chmod_tc001.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: chmod_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证chmod对文件和目录权限修改功能是否正常 +# 前置条件: /tmp目录可写 +# 用例步骤: +# 1. 使用chmod修改文件权限为600,检查权限位 +# 2. 使用chmod修改目录权限为700,检查权限位 +# 3. 使用chmod u+x为文件增加执行权限,检查权限位 +# 预期结果: +# 1. chmod 600后文件权限为600 +# 2. chmod 700后目录权限为700 +# 3. 增加执行权限后权限为700 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证chmod对文件和目录权限修改功能是否正常" + +tcnt=3 +TEST_DIR="/tmp/ts_chmod_tc001" +TEST_FILE="${TEST_DIR}/file.txt" + +setup() +{ + rm -rf "${TEST_DIR}" + mkdir -p "${TEST_DIR}" || tst_brk $TFAIL "创建 ${TEST_DIR} 失败" + echo "test chmod" > "${TEST_FILE}" || tst_brk $TFAIL "创建 ${TEST_FILE} 失败" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 chmod 600 修改文件权限" + chmod 600 "${TEST_FILE}" 2>/dev/null + local perm + perm=$(stat -c "%a" "${TEST_FILE}" 2>/dev/null) + if [ "${perm}" = "600" ]; then + tst_res $TPASS "文件权限修改为 600 成功" + else + tst_res $TFAIL "文件权限期望 600, 实际 ${perm}" + fi + ;; + 1) + tst_res $TINFO "测试 chmod 700 修改目录权限" + chmod 700 "${TEST_DIR}" 2>/dev/null + local perm + perm=$(stat -c "%a" "${TEST_DIR}" 2>/dev/null) + if [ "${perm}" = "700" ]; then + tst_res $TPASS "目录权限修改为 700 成功" + else + tst_res $TFAIL "目录权限期望 700, 实际 ${perm}" + fi + ;; + 2) + tst_res $TINFO "测试 chmod u+x 增加执行权限" + chmod 600 "${TEST_FILE}" 2>/dev/null + chmod u+x "${TEST_FILE}" 2>/dev/null + local perm + perm=$(stat -c "%a" "${TEST_FILE}" 2>/dev/null) + if [ "${perm}" = "700" ]; then + tst_res $TPASS "增加执行权限后权限为 700" + else + tst_res $TFAIL "增加执行权限后权限期望 700, 实际 ${perm}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/chown_tc001.sh b/os_smoke_src/testcases/baseos/command/chown_tc001.sh new file mode 100755 index 0000000..a7dcc7c --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/chown_tc001.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: chown_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证chown对文件属主和属组修改功能是否正常 +# 前置条件: 需要root权限, 系统已安装useradd/groupadd/chown +# 用例步骤: +# 1. 创建测试用户组和测试用户 +# 2. 使用chown修改文件属主和属组 +# 3. 使用stat检查属主和属组是否与预期一致 +# 预期结果: +# 1. 用户和用户组创建成功 +# 2. chown执行成功 +# 3. stat显示属主属组正确 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证chown对文件属主和属组修改功能是否正常" + +tcnt=3 +TEST_DIR="/tmp/ts_chown_tc001" +TEST_FILE="${TEST_DIR}/file.txt" +TEST_USER="ts_chown_user" +TEST_GROUP="ts_chown_group" + +setup() +{ + if ! check_root; then + tst_brk $TCONF "需要 root 权限" + fi + ensure_command "chown" "coreutils" + ensure_command "useradd" "shadow-utils" + + rm -rf "${TEST_DIR}" + mkdir -p "${TEST_DIR}" || tst_brk $TFAIL "创建 ${TEST_DIR} 失败" + echo "test chown" > "${TEST_FILE}" || tst_brk $TFAIL "创建测试文件失败" + + id "${TEST_USER}" >/dev/null 2>&1 && userdel -r "${TEST_USER}" >/dev/null 2>&1 + getent group "${TEST_GROUP}" >/dev/null 2>&1 && groupdel "${TEST_GROUP}" >/dev/null 2>&1 +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + if check_root; then + id "${TEST_USER}" >/dev/null 2>&1 && userdel -r "${TEST_USER}" >/dev/null 2>&1 + getent group "${TEST_GROUP}" >/dev/null 2>&1 && groupdel "${TEST_GROUP}" >/dev/null 2>&1 + fi +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "创建测试用户组 ${TEST_GROUP}" + groupadd "${TEST_GROUP}" 2>/dev/null + if getent group "${TEST_GROUP}" >/dev/null 2>&1; then + tst_res $TPASS "用户组 ${TEST_GROUP} 创建成功" + else + tst_res $TFAIL "用户组 ${TEST_GROUP} 创建失败" + fi + ;; + 1) + tst_res $TINFO "创建测试用户 ${TEST_USER}" + useradd -g "${TEST_GROUP}" -M "${TEST_USER}" 2>/dev/null + if id "${TEST_USER}" >/dev/null 2>&1; then + tst_res $TPASS "用户 ${TEST_USER} 创建成功" + else + tst_res $TFAIL "用户 ${TEST_USER} 创建失败" + fi + ;; + 2) + tst_res $TINFO "测试 chown 修改文件属主属组" + chown "${TEST_USER}:${TEST_GROUP}" "${TEST_FILE}" 2>/dev/null + local owner_group + owner_group=$(stat -c "%U:%G" "${TEST_FILE}" 2>/dev/null) + if [ "${owner_group}" = "${TEST_USER}:${TEST_GROUP}" ]; then + tst_res $TPASS "chown 修改属主属组成功" + else + tst_res $TFAIL "期望 ${TEST_USER}:${TEST_GROUP}, 实际 ${owner_group}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/cp_tc001.sh b/os_smoke_src/testcases/baseos/command/cp_tc001.sh new file mode 100755 index 0000000..b7f9718 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/cp_tc001.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cp_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证cp命令基础复制、保持属性复制和递归复制功能 +# 前置条件: /tmp目录可写 +# 用例步骤: +# 1. 使用cp复制文件,验证内容一致 +# 2. 使用cp -p保持属性复制,验证权限和时间戳一致 +# 3. 使用cp -r递归复制目录,验证子文件被复制 +# 预期结果: +# 1. 复制后内容一致 +# 2. 权限和修改时间一致 +# 3. 目录及子文件均被复制 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证cp命令基础复制、保持属性复制和递归复制功能" + +tcnt=3 +TEST_DIR="/tmp/ts_cmd_cp_tc001" +SRC_FILE="${TEST_DIR}/src.txt" +DST_FILE="${TEST_DIR}/dst.txt" +SRC_DIR="${TEST_DIR}/srcdir" +DST_DIR="${TEST_DIR}/dstdir" + +setup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + mkdir -p "${SRC_DIR}" "${DST_DIR}" || tst_brk $TFAIL "创建测试目录失败" + echo "cp test content" > "${SRC_FILE}" || tst_brk $TFAIL "创建源文件失败" + echo "dir file" > "${SRC_DIR}/file_in_dir.txt" || tst_brk $TFAIL "创建目录内文件失败" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 cp 普通文件复制" + cp "${SRC_FILE}" "${DST_FILE}" 2>/dev/null + if [ -f "${DST_FILE}" ] && cmp "${SRC_FILE}" "${DST_FILE}" >/dev/null 2>&1; then + tst_res $TPASS "cp 复制文件成功, 内容一致" + else + tst_res $TFAIL "cp 复制文件失败或内容不一致" + fi + ;; + 1) + tst_res $TINFO "测试 cp -p 保持属性复制" + local dst_p="${TEST_DIR}/dst_p.txt" + cp -p "${SRC_FILE}" "${dst_p}" 2>/dev/null + if [ ! -f "${dst_p}" ]; then + tst_res $TFAIL "cp -p 目标文件不存在" + return + fi + local src_mode dst_mode src_mtime dst_mtime + src_mode=$(stat -c '%a' "${SRC_FILE}" 2>/dev/null) + dst_mode=$(stat -c '%a' "${dst_p}" 2>/dev/null) + src_mtime=$(stat -c '%Y' "${SRC_FILE}" 2>/dev/null) + dst_mtime=$(stat -c '%Y' "${dst_p}" 2>/dev/null) + if [ "${src_mode}" = "${dst_mode}" ] && [ "${src_mtime}" = "${dst_mtime}" ]; then + tst_res $TPASS "cp -p 属性保持一致" + else + tst_res $TFAIL "cp -p 属性不一致: mode ${src_mode}/${dst_mode}, mtime ${src_mtime}/${dst_mtime}" + fi + ;; + 2) + tst_res $TINFO "测试 cp -r 递归目录复制" + cp -r "${SRC_DIR}" "${DST_DIR}/srcdir_copy" 2>/dev/null + if [ -d "${DST_DIR}/srcdir_copy" ] && [ -f "${DST_DIR}/srcdir_copy/file_in_dir.txt" ]; then + tst_res $TPASS "cp -r 递归复制目录成功" + else + tst_res $TFAIL "cp -r 递归复制目录失败" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/cut_tc001.sh b/os_smoke_src/testcases/baseos/command/cut_tc001.sh new file mode 100755 index 0000000..3f7cc7e --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/cut_tc001.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cut_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证cut命令按分隔符提取字段的基础功能。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 使用cut -d ':' -f1提取第一个字段,验证输出为所有键名。 +# 2. 使用cut -d ':' -f2提取第二个字段,验证输出为对应的数值。 +# 预期结果: +# 1. 提取第一个字段的输出为key1、key2、key3。 +# 2. 提取第二个字段的输出为val1、val2、val3。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证cut命令按分隔符提取字段的基础功能。" + +tcnt=2 +TEST_FILE="/tmp/ts_cmd_cut_tc001.txt" + +setup() +{ + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +key1:val1 +key2:val2 +key3:val3 +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "cut -d ':' -f1 提取第一个字段" + f1=$(cut -d ':' -f1 "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "cut -f1 执行失败" + return + fi + expected="key1 +key2 +key3" + if [ "${f1}" = "${expected}" ]; then + tst_res $TPASS "cut -f1 输出正确" + else + tst_res $TFAIL "cut -f1 输出不匹配: ${f1}" + fi + ;; + 1) + tst_res $TINFO "cut -d ':' -f2 提取第二个字段" + f2=$(cut -d ':' -f2 "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "cut -f2 执行失败" + return + fi + expected="val1 +val2 +val3" + if [ "${f2}" = "${expected}" ]; then + tst_res $TPASS "cut -f2 输出正确" + else + tst_res $TFAIL "cut -f2 输出不匹配: ${f2}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/dd_tc001.sh b/os_smoke_src/testcases/baseos/command/dd_tc001.sh new file mode 100755 index 0000000..07ab466 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/dd_tc001.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: dd_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证dd命令块复制和数据生成的基础功能 +# 前置条件: /tmp目录可写 +# 用例步骤: +# 1. 使用dd从/dev/zero生成指定大小文件,验证文件大小 +# 2. 使用dd复制文件,验证内容一致 +# 3. 使用dd的skip/count参数部分读取文件 +# 预期结果: +# 1. 生成的文件大小与预期一致 +# 2. 复制后文件内容一致 +# 3. 部分读取的数据大小正确 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证dd命令块复制和数据生成的基础功能" + +tcnt=3 +TEST_DIR="/tmp/ts_cmd_dd_tc001" +SRC_FILE="${TEST_DIR}/src.bin" +DST_FILE="${TEST_DIR}/dst.bin" +PART_FILE="${TEST_DIR}/part.bin" + +setup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + mkdir -p "${TEST_DIR}" || tst_brk $TFAIL "创建测试目录失败" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 dd 从 /dev/zero 生成文件" + dd if=/dev/zero of="${SRC_FILE}" bs=1K count=64 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "dd 生成文件执行失败" + return + fi + local size + size=$(stat -c '%s' "${SRC_FILE}" 2>/dev/null) + if [ "${size}" = "65536" ]; then + tst_res $TPASS "dd 生成文件大小正确: 65536 字节" + else + tst_res $TFAIL "dd 生成文件大小异常: 期望 65536, 实际 ${size}" + fi + ;; + 1) + tst_res $TINFO "测试 dd 复制文件" + dd if="${SRC_FILE}" of="${DST_FILE}" bs=4K 2>/dev/null + if cmp "${SRC_FILE}" "${DST_FILE}" >/dev/null 2>&1; then + tst_res $TPASS "dd 复制文件内容一致" + else + tst_res $TFAIL "dd 复制文件内容不一致" + fi + ;; + 2) + tst_res $TINFO "测试 dd skip/count 部分读取" + dd if="${SRC_FILE}" of="${PART_FILE}" bs=1K skip=10 count=5 2>/dev/null + local size + size=$(stat -c '%s' "${PART_FILE}" 2>/dev/null) + if [ "${size}" = "5120" ]; then + tst_res $TPASS "dd 部分读取大小正确: 5120 字节" + else + tst_res $TFAIL "dd 部分读取大小异常: 期望 5120, 实际 ${size}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/df_tc001.sh b/os_smoke_src/testcases/baseos/command/df_tc001.sh new file mode 100755 index 0000000..f871c3a --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/df_tc001.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: df_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证df命令查询文件系统磁盘使用情况的基础功能。 +# 前置条件: 以root用户执行。 +# 用例步骤: +# 1. 执行df -h命令查看所有挂载点的磁盘使用情况。 +# 2. 执行df -h /tmp命令查看/tmp所在文件系统信息。 +# 3. 执行df -T命令查看文件系统类型信息。 +# 预期结果: +# 1. df -h返回码为0且输出包含Filesystem和Use%。 +# 2. df -h /tmp返回码为0且输出至少2行。 +# 3. df -T返回码为0且输出包含Type列。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证df命令查询文件系统磁盘使用情况的基础功能。" + +tcnt=3 + +setup() +{ + : +} + +cleanup() +{ + : +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "df -h 查看所有文件系统" + output_all=$(df -h 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "df -h 执行失败" + return + fi + header=$(echo "${output_all}" | head -n 1) + if echo "${header}" | grep -q "Filesystem" && echo "${header}" | grep -q "Use%"; then + tst_res $TPASS "df -h 输出包含 Filesystem 和 Use%" + else + tst_res $TFAIL "df -h 表头缺少 Filesystem 或 Use%: ${header}" + fi + ;; + 1) + tst_res $TINFO "df -h /tmp 查看 /tmp 文件系统" + output_tmp=$(df -h /tmp 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "df -h /tmp 执行失败" + return + fi + lines=$(echo "${output_tmp}" | wc -l) + if [ "${lines}" -ge 2 ]; then + tst_res $TPASS "df -h /tmp 输出 ${lines} 行" + else + tst_res $TFAIL "df -h /tmp 输出行数不足: ${lines}" + fi + ;; + 2) + tst_res $TINFO "df -T 查看文件系统类型" + output_t=$(df -T 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "df -T 执行失败" + return + fi + if echo "${output_t}" | head -n 1 | grep -q "Type"; then + tst_res $TPASS "df -T 输出包含 Type 列" + else + tst_res $TFAIL "df -T 表头缺少 Type" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/du_tc001.sh b/os_smoke_src/testcases/baseos/command/du_tc001.sh new file mode 100755 index 0000000..bb8994a --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/du_tc001.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: du_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证du命令统计目录磁盘占用的基础功能 +# 前置条件: /tmp目录可写 +# 用例步骤: +# 1. 使用du -s统计目录大小,验证返回值>0 +# 2. 使用du -sh显示人类可读格式,验证输出包含目录路径 +# 预期结果: +# 1. du -s统计值大于0 +# 2. du -sh输出包含目录路径 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证du命令统计目录磁盘占用的基础功能" + +tcnt=2 +TEST_DIR="/tmp/ts_cmd_du_tc001" + +setup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + mkdir -p "${TEST_DIR}" || tst_brk $TFAIL "创建测试目录失败" + dd if=/dev/zero of="${TEST_DIR}/file1.bin" bs=1K count=10 >/dev/null 2>&1 + dd if=/dev/zero of="${TEST_DIR}/file2.bin" bs=1K count=5 >/dev/null 2>&1 +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 du -s 统计目录大小" + local output size + output=$(du -s "${TEST_DIR}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "du -s 执行失败" + return + fi + size=$(echo "${output}" | awk '{print $1}') + if [ -n "${size}" ] && [ "${size}" -gt 0 ] 2>/dev/null; then + tst_res $TPASS "du -s 统计值 ${size} > 0" + else + tst_res $TFAIL "du -s 统计值异常: ${size}" + fi + ;; + 1) + tst_res $TINFO "测试 du -sh 人类可读格式" + local output + output=$(du -sh "${TEST_DIR}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "du -sh 执行失败" + return + fi + if echo "${output}" | grep -q "${TEST_DIR}"; then + tst_res $TPASS "du -sh 输出包含目录路径" + else + tst_res $TFAIL "du -sh 输出缺少目录路径" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/egrep_tc001.sh b/os_smoke_src/testcases/baseos/command/egrep_tc001.sh new file mode 100755 index 0000000..b589a88 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/egrep_tc001.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: egrep_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证egrep命令(或grep -E)进行扩展正则匹配的基础功能。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 使用egrep 'foo|bar'匹配包含foo或bar的行。 +# 2. 使用egrep '^b.*[0-9]$'匹配以b开头并以数字结尾的行。 +# 预期结果: +# 1. 输出包含foo_line和bar_line。 +# 2. 输出仅包含b_line1。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证egrep命令(或grep -E)进行扩展正则匹配的基础功能。" + +tcnt=2 +TEST_FILE="/tmp/ts_cmd_egrep_tc001.txt" +EGREP_CMD="" + +setup() +{ + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +foo_line +bar_line +baz +b_line1 +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" + + ensure_command "grep" + if command -v egrep >/dev/null 2>&1; then + EGREP_CMD="egrep" + else + EGREP_CMD="grep -E" + fi +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "egrep 'foo|bar' 匹配" + out=$( ${EGREP_CMD} 'foo|bar' "${TEST_FILE}" 2>/dev/null ) + if [ $? -ne 0 ]; then + tst_res $TFAIL "egrep foo|bar 执行失败" + return + fi + local ok=1 + echo "${out}" | grep -q 'foo_line' || ok=0 + echo "${out}" | grep -q 'bar_line' || ok=0 + if [ $ok -eq 1 ]; then + tst_res $TPASS "egrep foo|bar 匹配正确" + else + tst_res $TFAIL "egrep foo|bar 输出不符: ${out}" + fi + ;; + 1) + tst_res $TINFO "egrep '^b.*[0-9]$' 匹配" + out=$( ${EGREP_CMD} '^b.*[0-9]$' "${TEST_FILE}" 2>/dev/null ) + if [ $? -ne 0 ]; then + tst_res $TFAIL "egrep ^b.*[0-9]$ 执行失败" + return + fi + if echo "${out}" | grep -qx 'b_line1'; then + tst_res $TPASS "egrep ^b.*[0-9]$ 匹配正确" + else + tst_res $TFAIL "egrep ^b.*[0-9]$ 输出不符: ${out}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/find_tc001.sh b/os_smoke_src/testcases/baseos/command/find_tc001.sh new file mode 100755 index 0000000..cd27555 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/find_tc001.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: find_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: 验证find命令按名称、类型查找文件的基础功能 +# 前置条件: /tmp目录可写 +# 用例步骤: +# 1. 使用find -name按名称查找文件 +# 2. 使用find -type f查找普通文件 +# 3. 使用find -type d查找目录 +# 预期结果: +# 1. 按名称能找到目标文件 +# 2. 按类型f只列出普通文件 +# 3. 按类型d只列出目录 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证find命令按名称、类型查找文件的基础功能" + +tcnt=3 +TEST_DIR="/tmp/ts_cmd_find_tc001" + +setup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + mkdir -p "${TEST_DIR}/sub1/sub2" || tst_brk $TFAIL "创建测试目录失败" + echo "find me" > "${TEST_DIR}/target.txt" + echo "nested" > "${TEST_DIR}/sub1/nested.txt" + echo "deep" > "${TEST_DIR}/sub1/sub2/deep.log" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 find -name 按名称查找" + local out + out=$(find "${TEST_DIR}" -name "target.txt" 2>/dev/null) + if echo "${out}" | grep -q "target.txt"; then + tst_res $TPASS "find -name 找到 target.txt" + else + tst_res $TFAIL "find -name 未找到 target.txt" + fi + ;; + 1) + tst_res $TINFO "测试 find -type f 查找普通文件" + local out + out=$(find "${TEST_DIR}" -type f 2>/dev/null) + local cnt + cnt=$(echo "${out}" | wc -l) + if [ "${cnt}" -eq 3 ]; then + tst_res $TPASS "find -type f 找到 3 个普通文件" + else + tst_res $TFAIL "find -type f 期望 3 个文件, 实际 ${cnt}" + fi + ;; + 2) + tst_res $TINFO "测试 find -type d 查找目录" + local out + out=$(find "${TEST_DIR}" -type d 2>/dev/null) + if echo "${out}" | grep -q "sub1" && echo "${out}" | grep -q "sub2"; then + tst_res $TPASS "find -type d 找到 sub1 和 sub2 目录" + else + tst_res $TFAIL "find -type d 缺少预期目录" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/fs_basic_tools_tc001.sh b/os_smoke_src/testcases/baseos/command/fs_basic_tools_tc001.sh new file mode 100755 index 0000000..881dcc4 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/fs_basic_tools_tc001.sh @@ -0,0 +1,139 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: fs_basic_tools_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例状态: 已完成 +# 用例描述: 基础文件和目录管理命令冒烟测试,覆盖创建、删除、复制、移动以及链接等操作。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 创建测试目录和文件,验证touch和mkdir成功。 +# 2. 使用cp和mv复制移动文件,验证内容一致。 +# 3. 使用ln创建硬链接和软链接,验证inode和指向。 +# 4. 使用ls/stat/df/du查看文件系统信息。 +# 5. 使用rm -rf清理测试目录。 +# 预期结果: +# 1. 文件和目录创建成功。 +# 2. 复制移动后内容一致。 +# 3. 硬链接inode一致,软链接指向正确。 +# 4. ls/stat/df/du执行成功。 +# 5. rm -rf清理成功。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="基础文件和目录管理命令冒烟测试,覆盖创建、删除、复制、移动以及链接等操作。" + +tcnt=5 +TEST_DIR="/tmp/ts_fs_basic_tools_tc001" +TEST_FILE="${TEST_DIR}/origin.txt" + +setup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + SAFE_MKDIR "${TEST_DIR}" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "创建测试文件和目录" + echo "fs basic tools test" > "${TEST_FILE}" 2>/dev/null + mkdir -p "${TEST_DIR}/subdir" 2>/dev/null + if [ -f "${TEST_FILE}" ] && [ -d "${TEST_DIR}/subdir" ]; then + tst_res $TPASS "文件和目录创建成功" + else + tst_res $TFAIL "文件或目录创建失败" + fi + ;; + 1) + tst_res $TINFO "cp 和 mv 操作" + [ -f "${TEST_FILE}" ] || echo "fs basic tools test" > "${TEST_FILE}" + [ -d "${TEST_DIR}/subdir" ] || mkdir -p "${TEST_DIR}/subdir" + cp "${TEST_FILE}" "${TEST_DIR}/copy.txt" 2>/dev/null + if [ ! -f "${TEST_DIR}/copy.txt" ]; then + tst_res $TFAIL "cp 复制文件失败" + return + fi + cmp "${TEST_FILE}" "${TEST_DIR}/copy.txt" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "cp 复制内容不一致" + return + fi + mv "${TEST_DIR}/copy.txt" "${TEST_DIR}/subdir/moved.txt" 2>/dev/null + if [ -f "${TEST_DIR}/subdir/moved.txt" ]; then + tst_res $TPASS "cp 和 mv 操作成功" + else + tst_res $TFAIL "mv 移动文件失败" + fi + ;; + 2) + tst_res $TINFO "ln 硬链接和软链接" + [ -f "${TEST_FILE}" ] || echo "fs basic tools test" > "${TEST_FILE}" + ln "${TEST_FILE}" "${TEST_DIR}/hardlink.txt" 2>/dev/null + ln -s "${TEST_FILE}" "${TEST_DIR}/softlink.txt" 2>/dev/null + local ok=1 + if [ ! -f "${TEST_DIR}/hardlink.txt" ]; then + tst_res $TFAIL "硬链接创建失败" + return + fi + if [ ! -L "${TEST_DIR}/softlink.txt" ]; then + tst_res $TFAIL "软链接创建失败" + return + fi + src_inode=$(stat -c '%i' "${TEST_FILE}" 2>/dev/null) + hard_inode=$(stat -c '%i' "${TEST_DIR}/hardlink.txt" 2>/dev/null) + [ "${src_inode}" = "${hard_inode}" ] || ok=0 + link_target=$(readlink "${TEST_DIR}/softlink.txt" 2>/dev/null) + [ "${link_target}" = "${TEST_FILE}" ] || ok=0 + if [ $ok -eq 1 ]; then + tst_res $TPASS "硬链接inode一致,软链接指向正确" + else + tst_res $TFAIL "链接验证失败: src_inode=${src_inode}, hard_inode=${hard_inode}, target=${link_target}" + fi + ;; + 3) + tst_res $TINFO "ls/stat/df/du 查看信息" + [ -f "${TEST_FILE}" ] || echo "fs basic tools test" > "${TEST_FILE}" + local ok=1 + ls -l "${TEST_DIR}" >/dev/null 2>&1 || ok=0 + stat "${TEST_FILE}" >/dev/null 2>&1 || ok=0 + df -h "${TEST_DIR}" >/dev/null 2>&1 || ok=0 + du_output=$(du -s "${TEST_DIR}" 2>/dev/null) + if [ -z "${du_output}" ]; then + ok=0 + else + du_size=$(echo "${du_output}" | awk '{print $1}') + [ "${du_size}" -gt 0 ] 2>/dev/null || ok=0 + fi + if [ $ok -eq 1 ]; then + tst_res $TPASS "ls/stat/df/du 执行成功" + else + tst_res $TFAIL "ls/stat/df/du 部分命令失败" + fi + ;; + 4) + tst_res $TINFO "rm -rf 清理测试目录" + rm -rf "${TEST_DIR}" 2>/dev/null + if [ -e "${TEST_DIR}" ]; then + tst_res $TFAIL "rm -rf 未能删除测试目录" + else + tst_res $TPASS "rm -rf 清理成功" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/grep_tc001.sh b/os_smoke_src/testcases/baseos/command/grep_tc001.sh new file mode 100755 index 0000000..7b5d8fd --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/grep_tc001.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: grep_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证grep命令在大小写匹配、反向匹配和行号显示等常用参数下的基础功能。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 使用grep apple精确匹配小写行。 +# 2. 使用grep -i apple不区分大小写匹配。 +# 3. 使用grep -v apple反向匹配。 +# 4. 使用grep -n cherry显示行号。 +# 预期结果: +# 1. 输出仅包含小写apple所在行。 +# 2. 输出行数等于2。 +# 3. 输出不包含apple。 +# 4. 输出行号为3。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证grep命令在大小写匹配、反向匹配和行号显示等常用参数下的基础功能。" + +tcnt=4 +TEST_FILE="/tmp/ts_cmd_grep_tc001.txt" + +setup() +{ + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +apple +Banana +cherry +APPLE pie +none +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" + ensure_command "grep" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "grep apple 精确匹配" + out=$(grep 'apple' "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "grep apple 执行失败" + return + fi + if echo "${out}" | grep -qx 'apple'; then + tst_res $TPASS "grep apple 输出正确" + else + tst_res $TFAIL "grep apple 输出不符: ${out}" + fi + ;; + 1) + tst_res $TINFO "grep -i apple 不区分大小写" + out=$(grep -i 'apple' "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "grep -i apple 执行失败" + return + fi + lines=$(echo "${out}" | wc -l) + if [ "${lines}" -eq 2 ]; then + tst_res $TPASS "grep -i apple 输出 ${lines} 行" + else + tst_res $TFAIL "grep -i apple 期望2行,实际 ${lines} 行" + fi + ;; + 2) + tst_res $TINFO "grep -iv apple 反向匹配" + out=$(grep -iv 'apple' "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "grep -iv apple 执行失败" + return + fi + if echo "${out}" | grep -i 'apple' >/dev/null 2>&1; then + tst_res $TFAIL "grep -iv 输出仍含 apple" + else + tst_res $TPASS "grep -iv apple 反向匹配正确" + fi + ;; + 3) + tst_res $TINFO "grep -n cherry 显示行号" + out=$(grep -n 'cherry' "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "grep -n cherry 执行失败" + return + fi + if echo "${out}" | grep -q '^3:'; then + tst_res $TPASS "grep -n cherry 行号正确(第3行)" + else + tst_res $TFAIL "grep -n cherry 行号不正确: ${out}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/gzip_tc001.sh b/os_smoke_src/testcases/baseos/command/gzip_tc001.sh new file mode 100755 index 0000000..02ade7f --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/gzip_tc001.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: gzip_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证gzip和gunzip命令对文件进行压缩和解压的基础功能 +# 前置条件: 系统已安装gzip/gunzip +# 用例步骤: +# 1. 使用gzip压缩文件(兼容老版本,不依赖-k选项),验证.gz文件生成且源文件存在 +# 2. 使用gunzip -c解压并比对内容 +# 预期结果: +# 1. .gz文件存在且非空,源文件未删除 +# 2. 解压后内容与原始一致 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证gzip和gunzip命令对文件进行压缩和解压的基础功能" + +tcnt=2 +TEST_DIR="/tmp/ts_cmd_gzip_tc001" +TEST_FILE="${TEST_DIR}/test.txt" +GZ_FILE="${TEST_FILE}.gz" +UNGZ_FILE="${TEST_DIR}/test_ungz.txt" + +setup() +{ + ensure_command "gzip" + rm -rf "${TEST_DIR}" 2>/dev/null + mkdir -p "${TEST_DIR}" || tst_brk $TFAIL "创建测试目录失败" + echo "gzip test content" > "${TEST_FILE}" || tst_brk $TFAIL "创建测试文件失败" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 gzip 压缩文件" + # 兼容老版本gzip(<1.6不支持-k),先备份源文件再压缩 + cp "${TEST_FILE}" "${TEST_FILE}.bak" + gzip "${TEST_FILE}" + if [ $? -ne 0 ]; then + tst_res $TFAIL "gzip 压缩命令执行失败" + return + fi + # 还原源文件 + cp "${TEST_FILE}.bak" "${TEST_FILE}" + local ok=1 + [ -s "${GZ_FILE}" ] || ok=0 + [ -f "${TEST_FILE}" ] || ok=0 + if [ $ok -eq 1 ]; then + tst_res $TPASS "gzip 压缩成功, .gz存在且源文件已还原" + else + tst_res $TFAIL "gzip 压缩异常" + fi + ;; + 1) + tst_res $TINFO "测试 gunzip 解压并比对内容" + gunzip -c "${GZ_FILE}" > "${UNGZ_FILE}" 2>/dev/null + if cmp "${TEST_FILE}" "${UNGZ_FILE}" >/dev/null 2>&1; then + tst_res $TPASS "解压后内容与原始一致" + else + tst_res $TFAIL "解压后内容与原始不一致" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/head_tc001.sh b/os_smoke_src/testcases/baseos/command/head_tc001.sh new file mode 100755 index 0000000..92f3c6e --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/head_tc001.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: head_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证head命令按行数截取文件开头内容的基础功能。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 使用head -n 1获取首行内容,验证为line1。 +# 2. 使用head -n 3获取前三行,验证行数为3且第三行为line3。 +# 预期结果: +# 1. head -n 1输出为line1。 +# 2. head -n 3输出3行,第三行为line3。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证head命令按行数截取文件开头内容的基础功能。" + +tcnt=2 +TEST_FILE="/tmp/ts_cmd_head_tc001.txt" + +setup() +{ + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +line1 +line2 +line3 +line4 +line5 +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "head -n 1 获取首行" + out=$(head -n 1 "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "head -n 1 执行失败" + return + fi + if [ "${out}" = "line1" ]; then + tst_res $TPASS "head -n 1 输出正确: line1" + else + tst_res $TFAIL "head -n 1 输出不符: ${out}" + fi + ;; + 1) + tst_res $TINFO "head -n 3 获取前三行" + out=$(head -n 3 "${TEST_FILE}" 2>/dev/null) + lines=$(echo "${out}" | wc -l) + third=$(echo "${out}" | sed -n '3p') + if [ "${lines}" -eq 3 ] && [ "${third}" = "line3" ]; then + tst_res $TPASS "head -n 3 输出正确: ${lines}行,第三行=${third}" + else + tst_res $TFAIL "head -n 3 输出不符: lines=${lines}, third=${third}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/kill_tc001.sh b/os_smoke_src/testcases/baseos/command/kill_tc001.sh new file mode 100755 index 0000000..0b0944a --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/kill_tc001.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: kill_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证kill命令向进程发送终止信号的基础功能 +# 前置条件: 系统已安装 kill 命令 +# 用例步骤: +# 1. 启动一个后台sleep测试进程并记录其PID +# 2. 使用kill向该进程发送TERM信号,验证进程退出 +# 预期结果: +# 1. sleep测试进程启动成功并获取到有效PID +# 2. kill命令返回码为0,进程已不在系统中存在 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "启动后台测试进程" + "kill 发送 TERM 信号终止进程" +) +tcnt=${#TCASE_DESC[@]} + +SLEEP_PID="" + +setup() +{ + return 0 +} + +cleanup() +{ + if [ -n "${SLEEP_PID}" ] && ps -p "${SLEEP_PID}" >/dev/null 2>&1; then + kill "${SLEEP_PID}" >/dev/null 2>&1 || true + fi +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "启动后台 sleep 进程" + sleep 300 >/dev/null 2>&1 & + SLEEP_PID=$! + if ps -p "${SLEEP_PID}" >/dev/null 2>&1; then + tst_res $TPASS "sleep 进程启动成功, PID=${SLEEP_PID}" + else + tst_res $TFAIL "sleep 进程未运行" + fi + ;; + 1) + tst_res $TINFO "kill ${SLEEP_PID}" + if [ -z "${SLEEP_PID}" ]; then + tst_res $TFAIL "无可用的测试进程 PID" + return + fi + kill "${SLEEP_PID}" 2>/dev/null + sleep 1 + if ps -p "${SLEEP_PID}" >/dev/null 2>&1; then + tst_res $TFAIL "kill 后进程仍存在" + else + tst_res $TPASS "kill 成功终止进程" + SLEEP_PID="" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/less_tc001.sh b/os_smoke_src/testcases/baseos/command/less_tc001.sh new file mode 100755 index 0000000..6bfe969 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/less_tc001.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: less_tc001 +# 用例类型: 功能测试 +# 优先级: P3 +# 用例状态: 已完成 +# 用例描述: 验证less命令在通过管道接收输入时能够正常输出并退出。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 通过管道将文件内容传入less,验证命令正常退出且输出包含首行。 +# 预期结果: +# 1. less命令返回码为0,输出包含line1。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证less命令在通过管道接收输入时能够正常输出并退出。" + +tcnt=1 +TEST_FILE="/tmp/ts_cmd_less_tc001.txt" + +setup() +{ + ensure_command "less" + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +line1 +line2 +line3 +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "通过管道调用 less" + out=$(cat "${TEST_FILE}" 2>/dev/null | less 2>/dev/null) + ret=$? + if [ ${ret} -ne 0 ]; then + tst_res $TFAIL "less 执行失败,返回码 ${ret}" + return + fi + if echo "${out}" | grep -q "line1"; then + tst_res $TPASS "less 输出包含 line1" + else + tst_res $TFAIL "less 输出缺少 line1" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/ln_tc001.sh b/os_smoke_src/testcases/baseos/command/ln_tc001.sh new file mode 100755 index 0000000..d3d6cde --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/ln_tc001.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ln_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证ln命令创建硬链接和软链接的基础功能 +# 前置条件: /tmp目录可写 +# 用例步骤: +# 1. 使用ln创建硬链接,验证inode一致 +# 2. 使用ln -s创建软链接,验证指向路径正确 +# 3. 删除源文件后验证硬链接可读,软链接失效 +# 预期结果: +# 1. 硬链接inode与源文件一致 +# 2. 软链接目标路径正确 +# 3. 硬链接仍可读,软链接访问失败 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证ln命令创建硬链接和软链接的基础功能" + +tcnt=3 +TEST_DIR="/tmp/ts_cmd_ln_tc001" +SRC_FILE="${TEST_DIR}/src.txt" +HARD_LINK="${TEST_DIR}/hardlink.txt" +SOFT_LINK="${TEST_DIR}/softlink.txt" + +setup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + mkdir -p "${TEST_DIR}" || tst_brk $TFAIL "创建测试目录失败" + echo "ln test content" > "${SRC_FILE}" || tst_brk $TFAIL "创建源文件失败" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 ln 创建硬链接" + ln "${SRC_FILE}" "${HARD_LINK}" 2>/dev/null + if [ ! -f "${HARD_LINK}" ]; then + tst_res $TFAIL "硬链接文件不存在" + return + fi + local src_inode hard_inode + src_inode=$(stat -c '%i' "${SRC_FILE}" 2>/dev/null) + hard_inode=$(stat -c '%i' "${HARD_LINK}" 2>/dev/null) + if [ "${src_inode}" = "${hard_inode}" ]; then + tst_res $TPASS "硬链接 inode 与源文件一致: ${src_inode}" + else + tst_res $TFAIL "inode 不一致: src=${src_inode}, hard=${hard_inode}" + fi + ;; + 1) + tst_res $TINFO "测试 ln -s 创建软链接" + ln -s "${SRC_FILE}" "${SOFT_LINK}" 2>/dev/null + if [ ! -L "${SOFT_LINK}" ]; then + tst_res $TFAIL "软链接不存在" + return + fi + local target + target=$(readlink "${SOFT_LINK}" 2>/dev/null) + if [ "${target}" = "${SRC_FILE}" ]; then + tst_res $TPASS "软链接目标路径正确" + else + tst_res $TFAIL "软链接目标路径错误: ${target}" + fi + ;; + 2) + tst_res $TINFO "删除源文件后验证链接行为" + rm -f "${SRC_FILE}" 2>/dev/null + local content + content=$(cat "${HARD_LINK}" 2>/dev/null) + local fail=0 + if [ "${content}" != "ln test content" ]; then + tst_res $TFAIL "硬链接内容在源文件删除后异常" + fail=1 + fi + if cat "${SOFT_LINK}" >/dev/null 2>&1; then + tst_res $TFAIL "软链接在源文件删除后仍可读" + fail=1 + fi + if [ $fail -eq 0 ]; then + tst_res $TPASS "硬链接可读, 软链接已失效" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/ls_tc001.sh b/os_smoke_src/testcases/baseos/command/ls_tc001.sh new file mode 100755 index 0000000..1027d9f --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/ls_tc001.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ls_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证ls命令普通列出、显示隐藏文件和长格式输出 +# 前置条件: /tmp目录可写 +# 用例步骤: +# 1. 使用ls列出目录,验证普通文件出现 +# 2. 使用ls -a,验证隐藏文件出现 +# 3. 使用ls -l和ls -lh,验证输出非空 +# 预期结果: +# 1. 普通文件名出现在输出中 +# 2. 隐藏文件名出现在输出中 +# 3. 长格式输出非空 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证ls命令普通列出、显示隐藏文件和长格式输出" + +tcnt=3 +TEST_DIR="/tmp/ts_cmd_ls_tc001" + +setup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + mkdir -p "${TEST_DIR}" || tst_brk $TFAIL "创建测试目录失败" + echo "visible" > "${TEST_DIR}/file1.txt" + echo "hidden" > "${TEST_DIR}/.hidden_file" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 ls 普通列出" + local output + output=$(ls "${TEST_DIR}" 2>/dev/null) + if echo "${output}" | grep -q "file1.txt"; then + tst_res $TPASS "ls 输出包含 file1.txt" + else + tst_res $TFAIL "ls 输出缺少 file1.txt" + fi + ;; + 1) + tst_res $TINFO "测试 ls -a 显示隐藏文件" + local output + output=$(ls -a "${TEST_DIR}" 2>/dev/null) + if echo "${output}" | grep -q ".hidden_file"; then + tst_res $TPASS "ls -a 输出包含 .hidden_file" + else + tst_res $TFAIL "ls -a 输出缺少 .hidden_file" + fi + ;; + 2) + tst_res $TINFO "测试 ls -l 和 ls -lh 长格式" + local output_l output_h + output_l=$(ls -l "${TEST_DIR}" 2>/dev/null) + output_h=$(ls -lh "${TEST_DIR}" 2>/dev/null) + if [ -n "${output_l}" ] && [ -n "${output_h}" ]; then + tst_res $TPASS "ls -l 和 ls -lh 输出非空" + else + tst_res $TFAIL "ls -l 或 ls -lh 输出为空" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/mkdir_tc001.sh b/os_smoke_src/testcases/baseos/command/mkdir_tc001.sh new file mode 100755 index 0000000..ac56451 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/mkdir_tc001.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: mkdir_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证mkdir命令创建目录及-p参数在已存在目录场景下的行为。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 使用mkdir创建一级目录。 +# 2. 使用mkdir -p创建多级嵌套目录。 +# 3. 对已存在目录执行mkdir预期失败,mkdir -p预期成功。 +# 预期结果: +# 1. mkdir创建目录成功且目标目录存在。 +# 2. mkdir -p创建多级目录成功且最内层目录存在。 +# 3. 已存在目录上mkdir返回非0,mkdir -p返回0。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证mkdir命令创建目录及-p参数在已存在目录场景下的行为。" + +tcnt=3 +TEST_DIR="/tmp/ts_cmd_mkdir_tc001" + +setup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + SAFE_MKDIR "${TEST_DIR}" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "mkdir 创建一级目录" + local d1="${TEST_DIR}/dir1" + mkdir "${d1}" 2>/dev/null + if [ $? -eq 0 ] && [ -d "${d1}" ]; then + tst_res $TPASS "mkdir 创建目录成功: ${d1}" + else + tst_res $TFAIL "mkdir 创建目录失败: ${d1}" + fi + ;; + 1) + tst_res $TINFO "mkdir -p 创建多级目录" + local d2="${TEST_DIR}/dir2/sub1/sub2" + mkdir -p "${d2}" 2>/dev/null + if [ $? -eq 0 ] && [ -d "${d2}" ]; then + tst_res $TPASS "mkdir -p 创建多级目录成功" + else + tst_res $TFAIL "mkdir -p 创建多级目录失败" + fi + ;; + 2) + tst_res $TINFO "mkdir 对已存在目录应失败,mkdir -p 应成功" + local d1="${TEST_DIR}/dir1" + [ -d "${d1}" ] || mkdir "${d1}" 2>/dev/null + mkdir "${d1}" 2>/dev/null + local ret1=$? + mkdir -p "${d1}" 2>/dev/null + local ret2=$? + if [ ${ret1} -ne 0 ] && [ ${ret2} -eq 0 ]; then + tst_res $TPASS "mkdir 失败(${ret1}), mkdir -p 成功(${ret2})" + else + tst_res $TFAIL "mkdir ret=${ret1}(期望非0), mkdir -p ret=${ret2}(期望0)" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/more_tc001.sh b/os_smoke_src/testcases/baseos/command/more_tc001.sh new file mode 100755 index 0000000..cfbbdb3 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/more_tc001.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: more_tc001 +# 用例类型: 功能测试 +# 优先级: P3 +# 用例状态: 已完成 +# 用例描述: 验证more命令在通过管道接收输入时能够正常输出并退出。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 通过管道将文件内容传入more,验证命令正常退出且输出包含首行。 +# 预期结果: +# 1. more命令返回码为0,输出包含line1。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证more命令在通过管道接收输入时能够正常输出并退出。" + +tcnt=1 +TEST_FILE="/tmp/ts_cmd_more_tc001.txt" + +setup() +{ + ensure_command "more" "util-linux" + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +line1 +line2 +line3 +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "通过管道调用 more" + out=$(cat "${TEST_FILE}" 2>/dev/null | more 2>/dev/null) + ret=$? + if [ ${ret} -ne 0 ]; then + tst_res $TFAIL "more 执行失败,返回码 ${ret}" + return + fi + if echo "${out}" | grep -q "line1"; then + tst_res $TPASS "more 输出包含 line1" + else + tst_res $TFAIL "more 输出缺少 line1" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/mv_tc001.sh b/os_smoke_src/testcases/baseos/command/mv_tc001.sh new file mode 100755 index 0000000..ac72875 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/mv_tc001.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: mv_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证mv命令重命名、跨目录移动和强制覆盖功能 +# 前置条件: /tmp目录可写 +# 用例步骤: +# 1. 使用mv重命名文件,验证旧文件消失、新文件存在 +# 2. 使用mv移动文件到目标目录 +# 3. 使用mv -f覆盖已有文件 +# 预期结果: +# 1. 重命名成功 +# 2. 跨目录移动成功 +# 3. 覆盖后内容为新内容 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证mv命令重命名、跨目录移动和强制覆盖功能" + +tcnt=3 +TEST_DIR="/tmp/ts_cmd_mv_tc001" +SRC_FILE="${TEST_DIR}/src.txt" +RENAMED_FILE="${TEST_DIR}/renamed.txt" +DST_DIR="${TEST_DIR}/dst" + +setup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + mkdir -p "${DST_DIR}" || tst_brk $TFAIL "创建测试目录失败" + echo "mv test content" > "${SRC_FILE}" || tst_brk $TFAIL "创建源文件失败" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 mv 重命名文件" + mv "${SRC_FILE}" "${RENAMED_FILE}" 2>/dev/null + if [ ! -e "${SRC_FILE}" ] && [ -f "${RENAMED_FILE}" ]; then + tst_res $TPASS "mv 重命名成功" + else + tst_res $TFAIL "mv 重命名失败" + fi + ;; + 1) + tst_res $TINFO "测试 mv 跨目录移动" + local dst_file="${DST_DIR}/moved.txt" + mv "${RENAMED_FILE}" "${dst_file}" 2>/dev/null + if [ ! -e "${RENAMED_FILE}" ] && [ -f "${dst_file}" ]; then + tst_res $TPASS "mv 跨目录移动成功" + else + tst_res $TFAIL "mv 跨目录移动失败" + fi + ;; + 2) + tst_res $TINFO "测试 mv -f 覆盖已有文件" + echo "old content" > "${DST_DIR}/overwrite.txt" + echo "new content" > "${TEST_DIR}/new_src.txt" + mv -f "${TEST_DIR}/new_src.txt" "${DST_DIR}/overwrite.txt" 2>/dev/null + local content + content=$(cat "${DST_DIR}/overwrite.txt" 2>/dev/null) + if [ "${content}" = "new content" ]; then + tst_res $TPASS "mv -f 覆盖成功" + else + tst_res $TFAIL "mv -f 覆盖后内容不正确" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/nl_tc001.sh b/os_smoke_src/testcases/baseos/command/nl_tc001.sh new file mode 100755 index 0000000..b5c1b28 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/nl_tc001.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: nl_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证nl命令为文本添加行号的基础功能。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 使用nl -ba为文件添加行号,验证首行行号为1且内容为line1。 +# 2. 验证总行数与原文件一致且行号连续递增。 +# 预期结果: +# 1. nl -ba输出第一行行号为1,内容为line1。 +# 2. 输出行数为3,行号从1连续递增。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证nl命令为文本添加行号的基础功能。" + +tcnt=2 +TEST_FILE="/tmp/ts_cmd_nl_tc001.txt" + +setup() +{ + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +line1 +line2 +line3 +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "nl -ba 验证首行行号和内容" + output=$(nl -ba "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "nl -ba 执行失败" + return + fi + first_line=$(echo "${output}" | head -n 1) + if echo "${first_line}" | grep -q "^[[:space:]]*1[[:space:]].*line1"; then + tst_res $TPASS "nl -ba 首行正确: ${first_line}" + else + tst_res $TFAIL "nl -ba 首行不符: ${first_line}" + fi + ;; + 1) + tst_res $TINFO "nl -ba 验证行数和行号连续性" + output=$(nl -ba "${TEST_FILE}" 2>/dev/null) + count=$(echo "${output}" | wc -l) + if [ "${count}" -ne 3 ]; then + tst_res $TFAIL "nl -ba 输出行数不符: ${count} (期望3)" + return + fi + local ok=1 + local expected=1 + echo "${output}" | awk '{print $1}' | while read n; do + n_clean=$(echo "$n" | tr -d ' ') + if [ "${n_clean}" -ne "${expected}" ] 2>/dev/null; then + echo "MISMATCH" + break + fi + expected=$((expected+1)) + done | grep -q "MISMATCH" && ok=0 + if [ $ok -eq 1 ]; then + tst_res $TPASS "nl -ba 行号连续递增,共 ${count} 行" + else + tst_res $TFAIL "nl -ba 行号不连续" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/pgrep_tc001.sh b/os_smoke_src/testcases/baseos/command/pgrep_tc001.sh new file mode 100755 index 0000000..71a93e8 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/pgrep_tc001.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: pgrep_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证pgrep命令按进程名匹配进程ID的基础功能 +# 前置条件: 系统已安装 pgrep 命令 +# 用例步骤: +# 1. 启动一个后台sleep测试进程并记录其PID +# 2. 使用pgrep -x sleep按精确进程名匹配,验证返回结果包含该PID +# 3. 停止该测试进程后再次执行pgrep -x sleep,验证不再包含该PID +# 预期结果: +# 1. sleep测试进程启动成功 +# 2. pgrep -x sleep输出中包含该PID +# 3. kill结束进程后,pgrep输出中不再包含该PID +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "启动后台测试进程" + "pgrep -x sleep 匹配进程" + "kill 后 pgrep 不再返回该 PID" +) +tcnt=${#TCASE_DESC[@]} + +SLEEP_PID="" + +setup() +{ + ensure_command "pgrep" "procps-ng" +} + +cleanup() +{ + if [ -n "${SLEEP_PID}" ] && ps -p "${SLEEP_PID}" >/dev/null 2>&1; then + kill "${SLEEP_PID}" >/dev/null 2>&1 || true + fi +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "启动后台 sleep 进程" + sleep 300 >/dev/null 2>&1 & + SLEEP_PID=$! + if ps -p "${SLEEP_PID}" >/dev/null 2>&1; then + tst_res $TPASS "sleep 进程启动成功, PID=${SLEEP_PID}" + else + tst_res $TFAIL "sleep 进程未运行" + fi + ;; + 1) + tst_res $TINFO "pgrep -x sleep 匹配进程" + local out + out=$(pgrep -x sleep 2>/dev/null || true) + if echo "${out}" | tr ' ' '\n' | grep -w "${SLEEP_PID}" >/dev/null 2>&1; then + tst_res $TPASS "pgrep 返回了 PID ${SLEEP_PID}" + else + tst_res $TFAIL "pgrep 未返回 PID ${SLEEP_PID}" + fi + ;; + 2) + tst_res $TINFO "kill 进程后验证 pgrep" + kill "${SLEEP_PID}" >/dev/null 2>&1 || true + sleep 1 + local out2 + out2=$(pgrep -x sleep 2>/dev/null || true) + if echo "${out2}" | tr ' ' '\n' | grep -w "${SLEEP_PID}" >/dev/null 2>&1; then + tst_res $TFAIL "pgrep 仍返回 PID ${SLEEP_PID}" + else + tst_res $TPASS "pgrep 不再返回 PID ${SLEEP_PID}" + SLEEP_PID="" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/ps_tc001.sh b/os_smoke_src/testcases/baseos/command/ps_tc001.sh new file mode 100755 index 0000000..64ef5eb --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/ps_tc001.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ps_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证ps命令按PID查询和列出系统进程的基础功能 +# 前置条件: 系统已安装 ps 命令 +# 用例步骤: +# 1. 使用ps -p $$ -o pid=查询当前shell进程 +# 2. 使用ps aux列出系统进程 +# 预期结果: +# 1. ps -p返回的PID与当前进程PID一致 +# 2. ps aux输出行数大于1且包含进程条目 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "ps -p 按PID查询进程" + "ps aux 列出系统进程" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + ensure_command "ps" "procps-ng" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "ps -p $$ -o pid=" + local cur_pid=$$ + local out_pid + out_pid=$(ps -p "${cur_pid}" -o pid= 2>/dev/null | tr -d ' ') + if [ $? -ne 0 ]; then + tst_res $TFAIL "ps -p 执行失败" + return + fi + if [ "${out_pid}" = "${cur_pid}" ]; then + tst_res $TPASS "ps -p 返回 PID 正确: ${out_pid}" + else + tst_res $TFAIL "ps -p 返回 ${out_pid}, 期望 ${cur_pid}" + fi + ;; + 1) + tst_res $TINFO "ps aux 列出系统进程" + local out_aux lines + out_aux=$(ps aux 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "ps aux 执行失败" + return + fi + lines=$(echo "${out_aux}" | wc -l) + if [ "${lines}" -le 1 ]; then + tst_res $TFAIL "ps aux 行数太少: ${lines}" + return + fi + if echo "${out_aux}" | grep -E "[p]s|[b]ash" >/dev/null 2>&1; then + tst_res $TPASS "ps aux 输出正常, 共 ${lines} 行" + else + tst_res $TFAIL "ps aux 输出缺少 ps 或 bash 进程条目" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/renice_tc001.sh b/os_smoke_src/testcases/baseos/command/renice_tc001.sh new file mode 100755 index 0000000..5902c9f --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/renice_tc001.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: renice_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证renice命令调整进程nice值的基础功能 +# 前置条件: 系统已安装 renice 命令 +# 用例步骤: +# 1. 启动一个后台sleep测试进程 +# 2. 使用renice命令将该进程的nice值调整为5 +# 3. 使用ps查询该PID的nice值,验证已不小于5 +# 预期结果: +# 1. sleep测试进程启动成功 +# 2. renice命令返回码为0 +# 3. 调整后ps输出的nice值大于或等于5 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "启动后台测试进程" + "renice 调整 nice 值为 5" + "验证 nice 值已调整" +) +tcnt=${#TCASE_DESC[@]} + +SLEEP_PID="" + +setup() +{ + ensure_command "renice" "util-linux" +} + +cleanup() +{ + if [ -n "${SLEEP_PID}" ] && ps -p "${SLEEP_PID}" >/dev/null 2>&1; then + kill "${SLEEP_PID}" >/dev/null 2>&1 || true + fi +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "启动后台 sleep 进程" + sleep 300 >/dev/null 2>&1 & + SLEEP_PID=$! + if ps -p "${SLEEP_PID}" >/dev/null 2>&1; then + tst_res $TPASS "sleep 进程启动成功, PID=${SLEEP_PID}" + else + tst_res $TFAIL "sleep 进程未运行" + fi + ;; + 1) + tst_res $TINFO "renice 5 -p ${SLEEP_PID}" + if renice 5 -p "${SLEEP_PID}" >/dev/null 2>&1; then + tst_res $TPASS "renice 执行成功" + else + tst_res $TFAIL "renice 执行失败" + fi + ;; + 2) + tst_res $TINFO "验证 nice 值" + local new_nice + new_nice=$(ps -p "${SLEEP_PID}" -o ni= 2>/dev/null | tr -d ' ') + if [ -z "${new_nice}" ]; then + tst_res $TFAIL "无法获取 nice 值" + elif [ "${new_nice}" -ge 5 ]; then + tst_res $TPASS "nice 值已调整为 ${new_nice} (>= 5)" + else + tst_res $TFAIL "nice 值 ${new_nice} < 5" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/rm_tc001.sh b/os_smoke_src/testcases/baseos/command/rm_tc001.sh new file mode 100755 index 0000000..4e8d633 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/rm_tc001.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: rm_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: 验证rm命令删除文件和目录的基础功能 +# 前置条件: /tmp目录可写 +# 用例步骤: +# 1. 使用rm -f删除单个文件,验证文件不存在 +# 2. 使用rm -rf递归删除目录,验证目录不存在 +# 3. 使用rm删除不存在的文件,验证返回非0 +# 预期结果: +# 1. 文件被删除 +# 2. 目录及内容被删除 +# 3. rm对不存在文件返回非0(rm -f则返回0) +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证rm命令删除文件和目录的基础功能" + +tcnt=3 +TEST_DIR="/tmp/ts_cmd_rm_tc001" + +setup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + mkdir -p "${TEST_DIR}/subdir" || tst_brk $TFAIL "创建测试目录失败" + echo "rm test" > "${TEST_DIR}/file1.txt" + echo "sub file" > "${TEST_DIR}/subdir/file2.txt" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 rm -f 删除单个文件" + rm -f "${TEST_DIR}/file1.txt" 2>/dev/null + if [ ! -e "${TEST_DIR}/file1.txt" ]; then + tst_res $TPASS "rm -f 删除文件成功" + else + tst_res $TFAIL "rm -f 删除文件失败, 文件仍存在" + fi + ;; + 1) + tst_res $TINFO "测试 rm -rf 递归删除目录" + rm -rf "${TEST_DIR}/subdir" 2>/dev/null + if [ ! -d "${TEST_DIR}/subdir" ]; then + tst_res $TPASS "rm -rf 递归删除目录成功" + else + tst_res $TFAIL "rm -rf 递归删除目录失败" + fi + ;; + 2) + tst_res $TINFO "测试 rm 删除不存在的文件" + rm "${TEST_DIR}/nonexist_file_xxx" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TPASS "rm 对不存在文件返回非0" + else + tst_res $TFAIL "rm 对不存在文件返回0, 异常" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/sed_tc001.sh b/os_smoke_src/testcases/baseos/command/sed_tc001.sh new file mode 100755 index 0000000..7d06a61 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/sed_tc001.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: sed_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证sed命令进行文本替换和删除行的基础功能。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 使用sed 's/foo/bar/'替换foo为bar。 +# 2. 使用sed '/^#/d'删除注释行。 +# 预期结果: +# 1. 替换后输出中foo被替换为bar。 +# 2. 删除后输出不包含#开头的行,非注释行保留。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证sed命令进行文本替换和删除行的基础功能。" + +tcnt=2 +TEST_FILE="/tmp/ts_cmd_sed_tc001.txt" + +setup() +{ + ensure_command "sed" + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +# comment line +foo line +bar line +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "sed 's/foo/bar/' 替换测试" + out=$(sed 's/foo/bar/' "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "sed 替换执行失败" + return + fi + if echo "${out}" | grep -q "bar line"; then + tst_res $TPASS "sed 替换正确,foo已被替换为bar" + else + tst_res $TFAIL "sed 替换后未找到 bar line" + fi + ;; + 1) + tst_res $TINFO "sed '/^#/d' 删除注释行" + out=$(sed '/^#/d' "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "sed 删除执行失败" + return + fi + if echo "${out}" | grep '^#' >/dev/null 2>&1; then + tst_res $TFAIL "sed 删除后仍存在注释行" + elif echo "${out}" | grep -q "foo line"; then + tst_res $TPASS "sed 删除注释行成功,非注释行保留" + else + tst_res $TFAIL "sed 删除后 foo line 也丢失" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/sort_tc001.sh b/os_smoke_src/testcases/baseos/command/sort_tc001.sh new file mode 100755 index 0000000..2c70bf4 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/sort_tc001.sh @@ -0,0 +1,106 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: sort_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证sort命令在默认排序、反向排序和去重排序等常用参数下的基础功能。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 使用sort默认排序,验证首行为a末行为d。 +# 2. 使用sort -r反向排序,验证首行为d末行为a。 +# 3. 使用sort -u去重排序,验证唯一行集合为a、b、c、d。 +# 预期结果: +# 1. 默认排序首行a末行d。 +# 2. 反向排序首行d末行a。 +# 3. 去重后输出为a b c d各一行。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证sort命令在默认排序、反向排序和去重排序等常用参数下的基础功能。" + +tcnt=3 +TEST_FILE="/tmp/ts_cmd_sort_tc001.txt" + +setup() +{ + ensure_command "sort" "coreutils" + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +c +a +d +b +b +c +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "sort 默认升序排列" + out=$(sort "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "sort 执行失败" + return + fi + first=$(echo "${out}" | head -n 1) + last=$(echo "${out}" | tail -n 1) + if [ "${first}" = "a" ] && [ "${last}" = "d" ]; then + tst_res $TPASS "sort 升序正确: 首=${first} 末=${last}" + else + tst_res $TFAIL "sort 升序不符: 首=${first} 末=${last}" + fi + ;; + 1) + tst_res $TINFO "sort -r 降序排列" + out=$(sort -r "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "sort -r 执行失败" + return + fi + first=$(echo "${out}" | head -n 1) + last=$(echo "${out}" | tail -n 1) + if [ "${first}" = "d" ] && [ "${last}" = "a" ]; then + tst_res $TPASS "sort -r 降序正确: 首=${first} 末=${last}" + else + tst_res $TFAIL "sort -r 降序不符: 首=${first} 末=${last}" + fi + ;; + 2) + tst_res $TINFO "sort -u 去重排序" + out=$(sort -u "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "sort -u 执行失败" + return + fi + expected="a +b +c +d" + if [ "${out}" = "${expected}" ]; then + tst_res $TPASS "sort -u 去重正确" + else + tst_res $TFAIL "sort -u 去重不符: ${out}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/stat_tc001.sh b/os_smoke_src/testcases/baseos/command/stat_tc001.sh new file mode 100755 index 0000000..f2b8af1 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/stat_tc001.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: stat_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证stat命令查询文件属性的基础功能 +# 前置条件: /tmp目录可写 +# 用例步骤: +# 1. 使用stat查看文件详细属性,验证输出包含文件名 +# 2. 使用stat -c格式化输出大小和权限,验证字段合法 +# 预期结果: +# 1. stat输出包含文件路径 +# 2. 文件大小>0,权限为3-4位八进制数 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证stat命令查询文件属性的基础功能" + +tcnt=2 +TEST_FILE="/tmp/ts_cmd_stat_tc001.txt" + +setup() +{ + rm -f "${TEST_FILE}" 2>/dev/null + echo "stat test" > "${TEST_FILE}" || tst_brk $TFAIL "创建测试文件失败" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 stat 默认输出" + local output + output=$(stat "${TEST_FILE}" 2>/dev/null) + if [ $? -eq 0 ] && echo "${output}" | grep -q "${TEST_FILE}"; then + tst_res $TPASS "stat 输出包含文件路径" + else + tst_res $TFAIL "stat 执行失败或输出缺少文件路径" + fi + ;; + 1) + tst_res $TINFO "测试 stat -c 格式化输出" + local formatted size perms + formatted=$(stat -c '%s %a' "${TEST_FILE}" 2>/dev/null) + size=$(echo "${formatted}" | awk '{print $1}') + perms=$(echo "${formatted}" | awk '{print $2}') + local ok=1 + [ -n "${size}" ] && [ "${size}" -gt 0 ] 2>/dev/null || ok=0 + echo "${perms}" | grep -Eq '^[0-7]{3,4}$' || ok=0 + if [ $ok -eq 1 ]; then + tst_res $TPASS "stat -c 输出合法: size=${size}, perms=${perms}" + else + tst_res $TFAIL "stat -c 输出异常: ${formatted}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/tail_tc001.sh b/os_smoke_src/testcases/baseos/command/tail_tc001.sh new file mode 100755 index 0000000..e08535c --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/tail_tc001.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: tail_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证tail命令按行数截取文件结尾内容的基础功能。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 使用tail -n 1获取末行,验证为line5。 +# 2. 使用tail -n 3获取最后三行,验证行数为3且首行为line3。 +# 预期结果: +# 1. tail -n 1输出为line5。 +# 2. tail -n 3输出3行,首行为line3。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证tail命令按行数截取文件结尾内容的基础功能。" + +tcnt=2 +TEST_FILE="/tmp/ts_cmd_tail_tc001.txt" + +setup() +{ + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +line1 +line2 +line3 +line4 +line5 +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "tail -n 1 获取末行" + out=$(tail -n 1 "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "tail -n 1 执行失败" + return + fi + if [ "${out}" = "line5" ]; then + tst_res $TPASS "tail -n 1 输出正确: line5" + else + tst_res $TFAIL "tail -n 1 输出不符: ${out}" + fi + ;; + 1) + tst_res $TINFO "tail -n 3 获取最后三行" + out=$(tail -n 3 "${TEST_FILE}" 2>/dev/null) + lines=$(echo "${out}" | wc -l) + first=$(echo "${out}" | sed -n '1p') + if [ "${lines}" -eq 3 ] && [ "${first}" = "line3" ]; then + tst_res $TPASS "tail -n 3 输出正确: ${lines}行,首行=${first}" + else + tst_res $TFAIL "tail -n 3 不符: lines=${lines}, first=${first}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/tar_tc001.sh b/os_smoke_src/testcases/baseos/command/tar_tc001.sh new file mode 100755 index 0000000..21bb4cc --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/tar_tc001.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: tar_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证tar命令创建归档、列出归档内容及解包功能 +# 前置条件: /tmp目录可写 +# 用例步骤: +# 1. 创建包含多个文件的测试目录 +# 2. 使用tar -cf创建归档文件,验证归档存在且大小>0 +# 3. 使用tar -tf列出归档内容,验证包含所有文件 +# 4. 使用tar -xf解包,验证解包后文件内容与原始一致 +# 预期结果: +# 1. tar打包成功 +# 2. 归档文件存在且非空 +# 3. 归档列表包含所有文件 +# 4. 解包后文件内容完全一致 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证tar命令创建归档、列出归档内容及解包功能" + +tcnt=3 +TEST_DIR="/tmp/ts_cmd_tar_tc001" +ARCHIVE="${TEST_DIR}/test.tar" +EXTRACT_DIR="${TEST_DIR}/extract" + +setup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + mkdir -p "${TEST_DIR}/src" "${EXTRACT_DIR}" || tst_brk $TFAIL "创建测试目录失败" + echo "file1 content" > "${TEST_DIR}/src/file1.txt" + echo "file2 content" > "${TEST_DIR}/src/file2.txt" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 tar -cf 创建归档" + tar -cf "${ARCHIVE}" -C "${TEST_DIR}/src" . 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "tar -cf 创建归档失败" + return + fi + if [ ! -s "${ARCHIVE}" ]; then + tst_res $TFAIL "归档文件不存在或大小为0" + return + fi + tst_res $TPASS "tar -cf 创建归档成功" + ;; + 1) + tst_res $TINFO "测试 tar -tf 列出归档内容" + local out + out=$(tar -tf "${ARCHIVE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "tar -tf 执行失败" + return + fi + local ok=1 + echo "${out}" | grep -q "file1.txt" || ok=0 + echo "${out}" | grep -q "file2.txt" || ok=0 + if [ $ok -eq 1 ]; then + tst_res $TPASS "tar -tf 列出内容包含所有文件" + else + tst_res $TFAIL "tar -tf 列出内容缺少文件: ${out}" + fi + ;; + 2) + tst_res $TINFO "测试 tar -xf 解包并验证内容" + tar -xf "${ARCHIVE}" -C "${EXTRACT_DIR}" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "tar -xf 解包失败" + return + fi + local fail=0 + cmp "${TEST_DIR}/src/file1.txt" "${EXTRACT_DIR}/file1.txt" >/dev/null 2>&1 || fail=1 + cmp "${TEST_DIR}/src/file2.txt" "${EXTRACT_DIR}/file2.txt" >/dev/null 2>&1 || fail=1 + if [ $fail -eq 0 ]; then + tst_res $TPASS "解包后文件内容与原始一致" + else + tst_res $TFAIL "解包后文件内容与原始不一致" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/text_tools_tc001.sh b/os_smoke_src/testcases/baseos/command/text_tools_tc001.sh new file mode 100755 index 0000000..5c67a83 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/text_tools_tc001.sh @@ -0,0 +1,109 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: text_tools_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例状态: 已完成 +# 用例描述: 文本查看与过滤相关基础命令冒烟测试,覆盖head、tail、wc、nl、grep、sort、uniq。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 创建包含多行和重复行的测试文件。 +# 2. 使用head和tail查看首尾行。 +# 3. 使用wc -l统计行数。 +# 4. 使用nl -ba为文本添加行号。 +# 5. 使用grep/sort/uniq统计重复行。 +# 预期结果: +# 1. 文件创建成功。 +# 2. 首行为line1,末行为last_line2。 +# 3. 行数为8。 +# 4. nl首行行号为1。 +# 5. dup_line出现2次。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="文本查看与过滤相关基础命令冒烟测试,覆盖head、tail、wc、nl、grep、sort、uniq。" + +tcnt=5 +TEST_FILE="/tmp/ts_text_tools_tc001.txt" + +setup() +{ + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +line1 +line2 +dup_line +dup_line +alpha beta +gamma delta +last_line1 +last_line2 +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "head/tail 首尾行验证" + first=$(head -n 1 "${TEST_FILE}" 2>/dev/null) + last=$(tail -n 1 "${TEST_FILE}" 2>/dev/null) + if [ "${first}" = "line1" ] && [ "${last}" = "last_line2" ]; then + tst_res $TPASS "head首行=line1, tail末行=last_line2" + else + tst_res $TFAIL "首行=${first}(期望line1), 末行=${last}(期望last_line2)" + fi + ;; + 1) + tst_res $TINFO "wc -l 统计行数" + count=$(wc -l < "${TEST_FILE}" 2>/dev/null) + if [ "${count}" = "8" ]; then + tst_res $TPASS "wc -l 行数正确: ${count}" + else + tst_res $TFAIL "wc -l 行数不符: ${count} (期望8)" + fi + ;; + 2) + tst_res $TINFO "nl -ba 添加行号" + first_nl=$(nl -ba "${TEST_FILE}" 2>/dev/null | head -n 1) + if echo "${first_nl}" | grep -q "^[[:space:]]*1[[:space:]].*line1"; then + tst_res $TPASS "nl -ba 首行行号正确" + else + tst_res $TFAIL "nl -ba 首行格式不符: ${first_nl}" + fi + ;; + 3) + tst_res $TINFO "grep 统计 dup_line 出现次数" + dup_count=$(grep -c '^dup_line$' "${TEST_FILE}" 2>/dev/null) + if [ "${dup_count}" = "2" ]; then + tst_res $TPASS "grep dup_line 计数正确: ${dup_count}" + else + tst_res $TFAIL "grep dup_line 计数不符: ${dup_count} (期望2)" + fi + ;; + 4) + tst_res $TINFO "sort|uniq -c 验证重复行统计" + uniq_line=$(sort "${TEST_FILE}" 2>/dev/null | uniq -c | grep "dup_line") + if echo "${uniq_line}" | grep -q "^[[:space:]]*2[[:space:]]"; then + tst_res $TPASS "sort|uniq -c dup_line 计数为2" + else + tst_res $TFAIL "sort|uniq -c dup_line 不符: ${uniq_line}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/touch_tc001.sh b/os_smoke_src/testcases/baseos/command/touch_tc001.sh new file mode 100755 index 0000000..47fc5b4 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/touch_tc001.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: touch_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证touch命令创建文件及更新时间戳的基础功能。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 使用touch创建空文件,验证文件存在且大小为0。 +# 2. 写入内容后再次touch,验证mtime已更新。 +# 3. 使用touch -c对不存在的文件执行,验证文件不会被创建。 +# 预期结果: +# 1. 文件创建成功,大小为0。 +# 2. touch后mtime大于之前的mtime。 +# 3. touch -c后文件不存在。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证touch命令创建文件及更新时间戳的基础功能。" + +tcnt=3 +TEST_DIR="/tmp/ts_cmd_touch_tc001" +TEST_FILE="${TEST_DIR}/file1.txt" + +setup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null + SAFE_MKDIR "${TEST_DIR}" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "touch 创建空文件" + touch "${TEST_FILE}" 2>/dev/null + if [ $? -ne 0 ] || [ ! -f "${TEST_FILE}" ]; then + tst_res $TFAIL "touch 创建文件失败" + return + fi + size=$(stat -c '%s' "${TEST_FILE}" 2>/dev/null) + if [ "${size}" -eq 0 ] 2>/dev/null; then + tst_res $TPASS "touch 创建空文件成功,大小=${size}" + else + tst_res $TFAIL "新文件大小不为0: ${size}" + fi + ;; + 1) + tst_res $TINFO "touch 更新文件 mtime" + [ -f "${TEST_FILE}" ] || touch "${TEST_FILE}" + echo "touch test" >> "${TEST_FILE}" 2>/dev/null + old_mtime=$(stat -c '%Y' "${TEST_FILE}" 2>/dev/null) + sleep 1 + touch "${TEST_FILE}" 2>/dev/null + new_mtime=$(stat -c '%Y' "${TEST_FILE}" 2>/dev/null) + if [ -n "${old_mtime}" ] && [ -n "${new_mtime}" ] && [ "${new_mtime}" -gt "${old_mtime}" ]; then + tst_res $TPASS "mtime 已更新: ${old_mtime} -> ${new_mtime}" + else + tst_res $TFAIL "mtime 未更新: old=${old_mtime}, new=${new_mtime}" + fi + ;; + 2) + tst_res $TINFO "touch -c 不创建不存在的文件" + local nofile="${TEST_DIR}/no_create.txt" + touch -c "${nofile}" 2>/dev/null + if [ -e "${nofile}" ]; then + tst_res $TFAIL "touch -c 意外创建了文件" + else + tst_res $TPASS "touch -c 未创建文件" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/tr_tc001.sh b/os_smoke_src/testcases/baseos/command/tr_tc001.sh new file mode 100755 index 0000000..0da4957 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/tr_tc001.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: tr_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证tr命令进行字符转换和删除的基础功能。 +# 前置条件: 以root用户执行。 +# 用例步骤: +# 1. 使用tr 'a-z' 'A-Z'将小写转大写。 +# 2. 使用tr -d删除指定字符。 +# 预期结果: +# 1. 输出为HELLO。 +# 2. 删除字符o后输出不包含o。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证tr命令进行字符转换和删除的基础功能。" + +tcnt=2 + +setup() +{ + ensure_command "tr" "coreutils" +} + +cleanup() +{ + : +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "tr 小写转大写" + upper=$(echo "hello" | tr 'a-z' 'A-Z') + if [ "${upper}" = "HELLO" ]; then + tst_res $TPASS "tr 大写转换正确: ${upper}" + else + tst_res $TFAIL "tr 大写转换不符: ${upper}" + fi + ;; + 1) + tst_res $TINFO "tr -d 删除字符" + no_o=$(echo "hello" | tr -d 'o') + if echo "${no_o}" | grep -q 'o'; then + tst_res $TFAIL "tr -d 未删除字符o: ${no_o}" + else + tst_res $TPASS "tr -d 删除字符o成功: ${no_o}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/uniq_tc001.sh b/os_smoke_src/testcases/baseos/command/uniq_tc001.sh new file mode 100755 index 0000000..1227c4c --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/uniq_tc001.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: uniq_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证uniq命令在去重和统计次数(-c)等常用参数下的基础功能。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 使用sort|uniq去重,验证唯一行集合为apple、banana、cherry。 +# 2. 使用sort|uniq -c统计次数,验证apple=2,banana=3,cherry=1。 +# 预期结果: +# 1. 去重后输出为apple banana cherry各一行。 +# 2. 统计次数正确。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证uniq命令在去重和统计次数(-c)等常用参数下的基础功能。" + +tcnt=2 +TEST_FILE="/tmp/ts_cmd_uniq_tc001.txt" + +setup() +{ + ensure_command "uniq" "coreutils" + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +apple +banana +banana +cherry +banana +apple +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "sort|uniq 去重" + out=$(sort "${TEST_FILE}" 2>/dev/null | uniq) + if [ $? -ne 0 ]; then + tst_res $TFAIL "sort|uniq 执行失败" + return + fi + expected="apple +banana +cherry" + if [ "${out}" = "${expected}" ]; then + tst_res $TPASS "sort|uniq 去重正确" + else + tst_res $TFAIL "sort|uniq 去重不符: ${out}" + fi + ;; + 1) + tst_res $TINFO "sort|uniq -c 统计次数" + out=$(sort "${TEST_FILE}" 2>/dev/null | uniq -c) + if [ $? -ne 0 ]; then + tst_res $TFAIL "sort|uniq -c 执行失败" + return + fi + local ok=1 + echo "${out}" | grep -Eq "^[[:space:]]*2[[:space:]]+apple" || ok=0 + echo "${out}" | grep -Eq "^[[:space:]]*3[[:space:]]+banana" || ok=0 + echo "${out}" | grep -Eq "^[[:space:]]*1[[:space:]]+cherry" || ok=0 + if [ $ok -eq 1 ]; then + tst_res $TPASS "uniq -c 统计正确: apple=2, banana=3, cherry=1" + else + tst_res $TFAIL "uniq -c 统计不符: ${out}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/wc_tc001.sh b/os_smoke_src/testcases/baseos/command/wc_tc001.sh new file mode 100755 index 0000000..0524cfb --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/wc_tc001.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: wc_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例状态: 已完成 +# 用例描述: 验证wc命令统计行数、单词数和字节数的基础功能。 +# 前置条件: 以root用户执行,/tmp目录可写。 +# 用例步骤: +# 1. 使用wc -l统计行数,验证为5行。 +# 2. 使用wc -w统计单词数,验证为8个。 +# 3. 使用wc -c统计字节数,验证大于0。 +# 预期结果: +# 1. wc -l输出5。 +# 2. wc -w输出8。 +# 3. wc -c输出大于0。 +# 作者: kevinzcheng +# 修改记录: Created on 2026-03-09 +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="验证wc命令统计行数、单词数和字节数的基础功能。" + +tcnt=3 +TEST_FILE="/tmp/ts_cmd_wc_tc001.txt" + +setup() +{ + rm -f "${TEST_FILE}" 2>/dev/null + cat > "${TEST_FILE}" << 'EOF' +hello world +this is +wc test +one +last +EOF + [ -f "${TEST_FILE}" ] || tst_brk $TFAIL "failed to create test file" +} + +cleanup() +{ + rm -f "${TEST_FILE}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "wc -l 统计行数" + lines=$(wc -l < "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "wc -l 执行失败" + return + fi + if [ "${lines}" = "5" ]; then + tst_res $TPASS "wc -l 行数正确: ${lines}" + else + tst_res $TFAIL "wc -l 行数不符: ${lines} (期望5)" + fi + ;; + 1) + tst_res $TINFO "wc -w 统计单词数" + words=$(wc -w < "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "wc -w 执行失败" + return + fi + if [ "${words}" = "8" ]; then + tst_res $TPASS "wc -w 单词数正确: ${words}" + else + tst_res $TFAIL "wc -w 单词数不符: ${words} (期望8)" + fi + ;; + 2) + tst_res $TINFO "wc -c 统计字节数" + bytes=$(wc -c < "${TEST_FILE}" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "wc -c 执行失败" + return + fi + if [ "${bytes}" -gt 0 ] 2>/dev/null; then + tst_res $TPASS "wc -c 字节数正确: ${bytes}" + else + tst_res $TFAIL "wc -c 字节数不符: ${bytes} (期望>0)" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/xz_tc001.sh b/os_smoke_src/testcases/baseos/command/xz_tc001.sh new file mode 100755 index 0000000..43877e2 --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/xz_tc001.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: xz_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证xz/unxz对文本文件的压缩与解压功能 +# 前置条件: 系统已安装xz与unxz +# 用例步骤: +# 1. 使用xz -k压缩测试文件,验证.xz文件生成且非空 +# 2. 使用unxz -k -c解压到新文件 +# 3. 比对原始文件与解压文件内容 +# 预期结果: +# 1. 压缩成功 +# 2. 解压成功 +# 3. 内容完全一致 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证xz/unxz对文本文件的压缩与解压功能" + +tcnt=3 +TEST_DIR="/tmp/ts_xz_tc001" +TEST_FILE="${TEST_DIR}/test.txt" +XZ_FILE="${TEST_FILE}.xz" +UNXZ_FILE="${TEST_DIR}/test_unxz.txt" + +setup() +{ + ensure_command "xz" + rm -rf "${TEST_DIR}" + mkdir -p "${TEST_DIR}" || tst_brk $TFAIL "创建 ${TEST_DIR} 失败" + echo -e "lineA\nlineB\nlineC" > "${TEST_FILE}" || tst_brk $TFAIL "创建测试文件失败" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 xz 压缩" + xz -k "${TEST_FILE}" 2>/dev/null + if [ -s "${XZ_FILE}" ]; then + tst_res $TPASS "xz 压缩成功, .xz 文件存在且非空" + else + tst_res $TFAIL "xz 压缩失败" + fi + ;; + 1) + tst_res $TINFO "测试 unxz 解压" + unxz -k -c "${XZ_FILE}" > "${UNXZ_FILE}" 2>/dev/null + if [ -s "${UNXZ_FILE}" ]; then + tst_res $TPASS "unxz 解压成功" + else + tst_res $TFAIL "unxz 解压失败" + fi + ;; + 2) + tst_res $TINFO "比较原始文件与解压文件内容" + if cmp "${TEST_FILE}" "${UNXZ_FILE}" >/dev/null 2>&1; then + tst_res $TPASS "内容一致" + else + tst_res $TFAIL "内容不一致" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/command/zip_tc001.sh b/os_smoke_src/testcases/baseos/command/zip_tc001.sh new file mode 100755 index 0000000..2914c2a --- /dev/null +++ b/os_smoke_src/testcases/baseos/command/zip_tc001.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: zip_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证zip/unzip对多个文件的打包压缩与解压功能 +# 前置条件: 系统已安装zip与unzip +# 用例步骤: +# 1. 使用zip打包多个文件 +# 2. 使用unzip解压 +# 3. 比对原始文件与解压后文件内容 +# 预期结果: +# 1. zip打包成功 +# 2. unzip解压成功 +# 3. 内容完全一致 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证zip/unzip对多个文件的打包压缩与解压功能" + +tcnt=3 +TEST_DIR="/tmp/ts_zip_tc001" +SRC_DIR="${TEST_DIR}/src" +DST_DIR="${TEST_DIR}/dst" +ZIP_FILE="${TEST_DIR}/files.zip" + +setup() +{ + ensure_command "zip" + ensure_command "unzip" + rm -rf "${TEST_DIR}" + mkdir -p "${SRC_DIR}" "${DST_DIR}" || tst_brk $TFAIL "创建测试目录失败" + echo -e "file1_line1\nfile1_line2" > "${SRC_DIR}/file1.txt" + echo -e "file2_line1\nfile2_line2" > "${SRC_DIR}/file2.txt" +} + +cleanup() +{ + rm -rf "${TEST_DIR}" 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "测试 zip 打包压缩" + (cd "${SRC_DIR}" && zip -q "${ZIP_FILE}" file1.txt file2.txt 2>/dev/null) + if [ -s "${ZIP_FILE}" ]; then + tst_res $TPASS "zip 打包成功, 文件非空" + else + tst_res $TFAIL "zip 打包失败或文件为空" + fi + ;; + 1) + tst_res $TINFO "测试 unzip 解压" + unzip -q "${ZIP_FILE}" -d "${DST_DIR}" 2>/dev/null + if [ -f "${DST_DIR}/file1.txt" ] && [ -f "${DST_DIR}/file2.txt" ]; then + tst_res $TPASS "unzip 解压成功, 文件存在" + else + tst_res $TFAIL "unzip 解压后文件缺失" + fi + ;; + 2) + tst_res $TINFO "比较原始文件与解压文件内容" + local fail=0 + cmp "${SRC_DIR}/file1.txt" "${DST_DIR}/file1.txt" >/dev/null 2>&1 || fail=1 + cmp "${SRC_DIR}/file2.txt" "${DST_DIR}/file2.txt" >/dev/null 2>&1 || fail=1 + if [ $fail -eq 0 ]; then + tst_res $TPASS "所有文件内容一致" + else + tst_res $TFAIL "文件内容不一致" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/network/Makefile b/os_smoke_src/testcases/baseos/network/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/baseos/network/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/baseos/network/dns_resolve_tc001.sh b/os_smoke_src/testcases/baseos/network/dns_resolve_tc001.sh new file mode 100755 index 0000000..b819977 --- /dev/null +++ b/os_smoke_src/testcases/baseos/network/dns_resolve_tc001.sh @@ -0,0 +1,117 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: dns_resolve_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: DNS解析功能验证 - 验证resolv.conf配置及本地/远程域名解析能力(支持单机离线) +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 检查/etc/resolv.conf存在且包含有效nameserver配置 +# 2. 使用getent hosts localhost验证本地解析链路可工作 +# 3. 验证/etc/nsswitch.conf中hosts条目包含dns +# 4. 尝试ping外网域名验证DNS解析+网络连通性(无外网时TCONF) +# 预期结果: +# 1. /etc/resolv.conf存在且至少有一个nameserver +# 2. getent hosts localhost返回127.0.0.1 +# 3. nsswitch.conf中hosts配置包含dns +# 4. 有外网时ping成功,无外网时跳过 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="DNS解析功能验证 - 验证resolv.conf配置及本地/远程域名解析能力(支持单机离线)" +tcnt=4 + +# 使用多个备选域名,避免单点依赖 +DNS_TEST_DOMAINS="mirrors.tencent.com mirrors.aliyun.com www.baidu.com" + +setup() +{ + : +} + +cleanup() +{ + : +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 检查/etc/resolv.conf + if [ ! -f /etc/resolv.conf ]; then + tst_res $TFAIL "/etc/resolv.conf不存在" + return + fi + local ns_count + ns_count=$(grep -c "^nameserver" /etc/resolv.conf 2>/dev/null) + if [ "$ns_count" -gt 0 ] 2>/dev/null; then + local first_ns + first_ns=$(grep "^nameserver" /etc/resolv.conf | head -1 | awk '{print $2}') + tst_res $TPASS "/etc/resolv.conf包含${ns_count}个nameserver(首个: ${first_ns})" + else + tst_res $TFAIL "/etc/resolv.conf中未找到nameserver配置" + fi + ;; + 1) + # getent hosts localhost验证本地解析链路 + local result + result=$(getent hosts localhost 2>/dev/null) + if [ $? -eq 0 ] && [ -n "$result" ]; then + local ip + ip=$(echo "$result" | awk '{print $1}' | head -1) + if echo "$ip" | grep -qE "^127\.0\.0\.1$|^::1$"; then + tst_res $TPASS "getent hosts localhost解析成功: ${ip}" + else + tst_res $TFAIL "getent hosts localhost返回异常IP: ${ip}" + fi + else + tst_res $TFAIL "getent hosts localhost解析失败" + fi + ;; + 2) + # 检查nsswitch.conf中的dns配置 + if [ ! -f /etc/nsswitch.conf ]; then + tst_res $TFAIL "/etc/nsswitch.conf不存在" + return + fi + local hosts_line + hosts_line=$(grep "^hosts:" /etc/nsswitch.conf 2>/dev/null) + if [ -z "$hosts_line" ]; then + tst_res $TFAIL "/etc/nsswitch.conf中未找到hosts配置行" + return + fi + if echo "$hosts_line" | grep -q "dns"; then + tst_res $TPASS "nsswitch.conf hosts配置包含dns: ${hosts_line}" + else + tst_res $TFAIL "nsswitch.conf hosts配置不包含dns: ${hosts_line}" + fi + ;; + 3) + # ping外网域名验证端到端连通性(无外网时TCONF) + local pinged=0 + local domain + for domain in $DNS_TEST_DOMAINS; do + if ping -c 1 -W 5 "$domain" >/dev/null 2>&1; then + tst_res $TPASS "ping ${domain}成功,DNS解析+网络连通性正常" + pinged=1 + break + fi + done + if [ $pinged -eq 0 ]; then + tst_res $TCONF "无法ping通外网域名(可能无外网环境),跳过" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/network/ip_tool_basic_tc001.sh b/os_smoke_src/testcases/baseos/network/ip_tool_basic_tc001.sh new file mode 100755 index 0000000..b7393e5 --- /dev/null +++ b/os_smoke_src/testcases/baseos/network/ip_tool_basic_tc001.sh @@ -0,0 +1,138 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ip_tool_basic_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: ip命令综合能力验证 - 验证ip addr/link/neigh/route等核心子命令及默认网关连通性 +# 前置条件: 系统具备root权限,网络已配置 +# 步骤: +# 1. ip addr show验证lo回环接口状态为UP且有127.0.0.1地址 +# 2. ip link show验证至少存在一个非lo的UP状态网络接口 +# 3. ip route show验证存在default默认路由 +# 4. ping默认网关验证连通性 +# 5. ip neigh show验证ARP/NDP邻居表可查询 +# 预期结果: +# 1. lo接口状态UP且绑定127.0.0.1 +# 2. 至少一个非lo接口状态为UP +# 3. 存在default路由条目 +# 4. 默认网关可ping通 +# 5. ip neigh命令可正常执行 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="ip命令综合能力验证 - 验证ip addr/link/neigh/route等核心子命令及默认网关连通性" +tcnt=5 + +setup() +{ + ensure_command "ip" "iproute" +} + +cleanup() +{ + : +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 验证lo回环接口 + local lo_out + lo_out=$(ip addr show lo 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "ip addr show lo执行失败" + return + fi + local fail=0 + if ! echo "$lo_out" | grep -q "UP"; then + tst_res $TFAIL "lo接口状态非UP" + fail=1 + fi + if ! echo "$lo_out" | grep -q "127.0.0.1"; then + tst_res $TFAIL "lo接口未绑定127.0.0.1" + fail=1 + fi + if [ $fail -eq 0 ]; then + tst_res $TPASS "lo回环接口状态UP,绑定127.0.0.1" + fi + ;; + 1) + # 验证至少一个非lo接口UP + local up_ifaces + up_ifaces=$(ip -o link show up 2>/dev/null | awk -F': ' '{print $2}' | grep -v "^lo$") + if [ -z "$up_ifaces" ]; then + tst_res $TFAIL "未发现除lo外的UP状态网络接口" + return + fi + local first_iface + first_iface=$(echo "$up_ifaces" | head -1) + local iface_count + iface_count=$(echo "$up_ifaces" | wc -l) + tst_res $TPASS "发现${iface_count}个非lo的UP接口(首个: ${first_iface})" + ;; + 2) + # 验证默认路由 + local def_route + def_route=$(ip route show default 2>/dev/null) + if [ -z "$def_route" ]; then + tst_res $TFAIL "未找到default默认路由" + return + fi + local gw + gw=$(echo "$def_route" | head -1 | grep -oP 'via \K[^ ]+') + local dev + dev=$(echo "$def_route" | head -1 | grep -oP 'dev \K[^ ]+') + tst_res $TPASS "存在默认路由: gateway=${gw} dev=${dev}" + ;; + 3) + # ping默认网关 + local gw + gw=$(ip route show default 2>/dev/null | head -1 | grep -oP 'via \K[^ ]+') + if [ -z "$gw" ]; then + tst_res $TCONF "无默认网关,跳过ping测试" + return + fi + if ping -c 3 -W 5 "$gw" >/dev/null 2>&1; then + tst_res $TPASS "默认网关${gw}可ping通" + else + tst_res $TFAIL "默认网关${gw}不可ping通" + fi + ;; + 4) + # 验证ip neigh + local neigh_out + neigh_out=$(ip neigh show 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "ip neigh show执行失败" + return + fi + # 先ping网关触发ARP,再查看邻居表 + local gw + gw=$(ip route show default 2>/dev/null | head -1 | grep -oP 'via \K[^ ]+') + if [ -n "$gw" ]; then + ping -c 1 -W 3 "$gw" >/dev/null 2>&1 + sleep 1 + fi + local neigh_count + neigh_count=$(ip neigh show 2>/dev/null | grep -cE "REACHABLE|STALE|DELAY|PROBE") + if [ "$neigh_count" -gt 0 ] 2>/dev/null; then + tst_res $TPASS "ip neigh显示${neigh_count}条有效邻居记录" + else + # 有些环境没有邻居记录但命令能跑,也算通过 + tst_res $TPASS "ip neigh命令可正常执行(邻居表条目${neigh_count})" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/network/netstat_tc001.sh b/os_smoke_src/testcases/baseos/network/netstat_tc001.sh new file mode 100755 index 0000000..0835461 --- /dev/null +++ b/os_smoke_src/testcases/baseos/network/netstat_tc001.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: netstat_tc001 +# 用例类型: 功能测试 +# 优先级: P3 +# 用例描述: 验证netstat命令列出网络连接信息的基础功能 +# 前置条件: 系统已安装 netstat 命令 (net-tools) +# 用例步骤: +# 1. 执行netstat -an命令查看所有网络连接 +# 2. 验证命令执行成功且输出包含协议信息 +# 预期结果: +# 1. netstat命令执行成功,返回码为0 +# 2. 输出包含Proto列标题或tcp/udp等协议关键字 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC="验证netstat命令列出网络连接信息的基础功能" +tcnt=1 + +setup() +{ + ensure_command "netstat" "net-tools" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "执行 netstat -an" + local out + out=$(netstat -an 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "netstat -an 执行失败" + return + fi + if echo "${out}" | head -n 1 | grep -qi "proto" || echo "${out}" | grep -qiE "tcp|udp"; then + tst_res $TPASS "netstat -an 输出包含协议信息" + else + tst_res $TFAIL "netstat -an 输出缺少协议信息" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/network/network_manager_tc001.sh b/os_smoke_src/testcases/baseos/network/network_manager_tc001.sh new file mode 100755 index 0000000..bd3cb26 --- /dev/null +++ b/os_smoke_src/testcases/baseos/network/network_manager_tc001.sh @@ -0,0 +1,128 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: network_manager_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: NetworkManager服务管理验证 - 验证NM服务启停和nmcli设备/连接管理能力 +# 前置条件: 系统已安装NetworkManager +# 步骤: +# 1. 验证NetworkManager服务处于active状态 +# 2. 重启NetworkManager服务并验证恢复running +# 3. nmcli general status验证NM整体状态 +# 4. nmcli device status验证至少存在一个已连接的网络设备 +# 5. nmcli connection show验证至少存在一个活跃连接 +# 预期结果: +# 1. NetworkManager服务为active(running) +# 2. 重启成功后服务恢复running +# 3. nmcli general显示NM状态正常 +# 4. 至少一个设备状态为connected +# 5. 至少一个连接处于活跃状态 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="NetworkManager服务管理验证 - 验证NM服务启停和nmcli设备/连接管理能力" +tcnt=5 + +setup() +{ + install_packages "NetworkManager" + systemctl is-active NetworkManager >/dev/null 2>&1 || { + systemctl start NetworkManager 2>/dev/null + sleep 3 + } +} + +cleanup() +{ + # 确保NM服务处于运行状态 + systemctl is-active NetworkManager >/dev/null 2>&1 || { + systemctl start NetworkManager 2>/dev/null + sleep 3 + } +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if systemctl is-active NetworkManager >/dev/null 2>&1; then + tst_res $TPASS "NetworkManager服务处于active状态" + else + tst_res $TFAIL "NetworkManager服务未处于active状态" + fi + ;; + 1) + systemctl restart NetworkManager 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "systemctl restart NetworkManager执行失败" + return + fi + sleep 5 + if systemctl status NetworkManager 2>/dev/null | grep -q "running"; then + tst_res $TPASS "NetworkManager重启成功,状态为running" + else + tst_res $TFAIL "NetworkManager重启后未恢复running" + fi + ;; + 2) + local gen_out + gen_out=$(nmcli general status 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "nmcli general status执行失败" + return + fi + if echo "$gen_out" | grep -qiE "connected|已连接"; then + tst_res $TPASS "nmcli general显示网络状态正常" + else + tst_res $TFAIL "nmcli general未显示connected状态" + fi + ;; + 3) + local dev_out + dev_out=$(nmcli device status 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "nmcli device status执行失败" + return + fi + if echo "$dev_out" | grep -qiE "connected|已连接" | grep -viE "disconnected|未连接"; then + tst_res $TPASS "nmcli device显示至少一个设备已连接" + else + # 备用检测:排除disconnected后检查connected + local connected_devs + connected_devs=$(echo "$dev_out" | grep -viE "DEVICE|disconnected|unavailable|unmanaged|未连接" | grep -ciE "connected|已连接") + if [ "$connected_devs" -gt 0 ] 2>/dev/null; then + tst_res $TPASS "nmcli device显示${connected_devs}个设备已连接" + else + tst_res $TFAIL "nmcli device未发现已连接的设备" + fi + fi + ;; + 4) + local conn_out + conn_out=$(nmcli connection show --active 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "nmcli connection show --active执行失败" + return + fi + # 排除表头,看是否有活跃连接 + local active_cnt + active_cnt=$(echo "$conn_out" | grep -cv "^NAME") + if [ "$active_cnt" -gt 0 ] 2>/dev/null; then + tst_res $TPASS "nmcli显示${active_cnt}个活跃连接" + else + tst_res $TFAIL "nmcli未发现活跃连接" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/network/nic_tool_cfg_tc001.sh b/os_smoke_src/testcases/baseos/network/nic_tool_cfg_tc001.sh new file mode 100755 index 0000000..0e7f907 --- /dev/null +++ b/os_smoke_src/testcases/baseos/network/nic_tool_cfg_tc001.sh @@ -0,0 +1,129 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: nic_tool_cfg_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 网络接口配置工具验证 - 验证ip/ifconfig对虚拟网卡的IP设置、路由管理能力 +# 前置条件: 系统具备root权限,iproute/net-tools已安装 +# 步骤: +# 1. 验证ip和ifconfig命令可用 +# 2. 创建bridge虚拟网卡并用ifconfig设置IP,ping验证连通性 +# 3. 用ip addr验证网卡IP配置正确 +# 4. 添加静态路由并用ip route验证,然后删除 +# 5. 清理虚拟网卡,验证删除成功 +# 预期结果: +# 1. ip和ifconfig命令均可用 +# 2. 虚拟网卡创建成功,IP设置后可ping通 +# 3. ip addr输出中包含设置的IP地址 +# 4. 路由添加成功且可查询到,删除成功 +# 5. 虚拟网卡删除后不再存在 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="网络接口配置工具验证 - 验证ip/ifconfig对虚拟网卡的IP设置、路由管理能力" +tcnt=5 + +NIC_NAME="nictst$$" +NIC_IP="192.168.249.1" +NIC_MASK="255.255.255.0" +ROUTE_NET="10.249.0.0/24" + +setup() +{ + ensure_command "ip" "iproute" + ensure_command "ifconfig" "net-tools" +} + +cleanup() +{ + ip link show "$NIC_NAME" &>/dev/null && { + ip link set "$NIC_NAME" down 2>/dev/null + ip link delete "$NIC_NAME" type bridge 2>/dev/null + } +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local fail=0 + for cmd in ip ifconfig ping; do + if ! which "$cmd" >/dev/null 2>&1; then + tst_res $TFAIL "${cmd}命令不可用" + fail=1 + fi + done + if [ $fail -eq 0 ]; then + tst_res $TPASS "ip/ifconfig/ping命令均可用" + fi + ;; + 1) + # 创建bridge虚拟网卡并设置IP + ip link add name "$NIC_NAME" type bridge 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "创建bridge网卡${NIC_NAME}失败" + return + fi + ip link set "$NIC_NAME" up 2>/dev/null + ifconfig "$NIC_NAME" "$NIC_IP" netmask "$NIC_MASK" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "ifconfig设置IP失败" + return + fi + sleep 1 + if ping -c 2 -W 3 "$NIC_IP" >/dev/null 2>&1; then + tst_res $TPASS "虚拟网卡${NIC_NAME}创建成功,IP=${NIC_IP}可ping通" + else + tst_res $TFAIL "虚拟网卡IP=${NIC_IP} ping不通" + fi + ;; + 2) + # 用ip addr验证IP配置 + local addr_out + addr_out=$(ip addr show "$NIC_NAME" 2>/dev/null) + if echo "$addr_out" | grep -q "$NIC_IP"; then + tst_res $TPASS "ip addr show确认${NIC_NAME}包含IP=${NIC_IP}" + else + tst_res $TFAIL "ip addr show未找到${NIC_IP}" + fi + ;; + 3) + # 添加静态路由并验证 + ip route add "$ROUTE_NET" via "$NIC_IP" dev "$NIC_NAME" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "添加静态路由${ROUTE_NET}失败" + return + fi + if ip route show | grep -q "$ROUTE_NET"; then + tst_res $TPASS "静态路由${ROUTE_NET}添加成功并可查询" + else + tst_res $TFAIL "静态路由${ROUTE_NET}添加后未找到" + return + fi + # 清理路由 + ip route del "$ROUTE_NET" via "$NIC_IP" dev "$NIC_NAME" 2>/dev/null + ;; + 4) + # 清理虚拟网卡 + ip link set "$NIC_NAME" down 2>/dev/null + ip link delete "$NIC_NAME" type bridge 2>/dev/null + sleep 1 + if ip link show "$NIC_NAME" &>/dev/null; then + tst_res $TFAIL "虚拟网卡${NIC_NAME}删除后仍存在" + else + tst_res $TPASS "虚拟网卡${NIC_NAME}删除成功" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/network/ping_tc001.sh b/os_smoke_src/testcases/baseos/network/ping_tc001.sh new file mode 100755 index 0000000..cddff35 --- /dev/null +++ b/os_smoke_src/testcases/baseos/network/ping_tc001.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ping_tc001 +# 用例类型: 功能测试 +# 优先级: P3 +# 用例描述: 验证ping命令对本地回环地址进行连通性测试的基础功能 +# 前置条件: 网络协议栈正常 +# 用例步骤: +# 1. 使用ping -c 1 127.0.0.1测试本地回环地址连通性 +# 预期结果: +# 1. ping命令执行成功,输出包含发送和接收报文统计 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC="验证ping命令对本地回环地址进行连通性测试的基础功能" +tcnt=1 + +setup() +{ + ensure_command "ping" "iputils" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "ping -c 1 127.0.0.1" + local out + out=$(ping -c 1 127.0.0.1 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "ping 127.0.0.1 失败" + return + fi + local fail=0 + echo "${out}" | grep -qi "1 packets transmitted" || fail=1 + echo "${out}" | grep -qi "1 received" || fail=1 + if [ $fail -eq 0 ]; then + tst_res $TPASS "ping 127.0.0.1 成功, 1 发送 1 接收" + else + tst_res $TFAIL "ping 输出缺少 transmitted/received 统计" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/network/tcp_listen_tc001.sh b/os_smoke_src/testcases/baseos/network/tcp_listen_tc001.sh new file mode 100755 index 0000000..10f0acf --- /dev/null +++ b/os_smoke_src/testcases/baseos/network/tcp_listen_tc001.sh @@ -0,0 +1,150 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: tcp_listen_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: TCP协议栈基础验证 - 验证ss/netstat LISTEN状态及nc工具TCP收发能力 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 使用ss -tlnp验证系统存在LISTEN状态端口 +# 2. 使用netstat -tlnp验证输出与ss一致(LISTEN端口存在) +# 3. 用nc创建临时TCP监听,验证ss能看到该端口 +# 4. 用nc客户端连接并发送数据,验证服务端收到 +# 5. 清理nc进程,验证端口释放 +# 预期结果: +# 1. ss -tlnp输出中存在LISTEN状态端口 +# 2. netstat -tlnp输出中存在LISTEN状态端口 +# 3. nc监听后ss可看到对应端口LISTEN +# 4. TCP数据收发成功 +# 5. nc进程清理后端口不再LISTEN +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="TCP协议栈基础验证 - 验证ss/netstat LISTEN状态及nc工具TCP收发能力" +tcnt=5 + +NC_PID="" +TEST_PORT="" +NC_RECV_FILE="" + +_find_free_port() +{ + local port + for port in $(seq 19700 19800); do + if ! ss -tlnp 2>/dev/null | grep -q ":${port} "; then + echo "$port" + return 0 + fi + done + return 1 +} + +setup() +{ + ensure_command "ss" "iproute" + ensure_command "nc" "nmap-ncat" + TEST_PORT=$(_find_free_port) + if [ -z "$TEST_PORT" ]; then + tst_brk $TCONF "无法找到可用端口(19700-19800)" + fi + NC_RECV_FILE="/tmp/tcp_listen_tc001_recv.$$" +} + +cleanup() +{ + [ -n "$NC_PID" ] && kill "$NC_PID" 2>/dev/null + # 清理可能残留的nc监听 + if [ -n "$TEST_PORT" ]; then + local pids + pids=$(ss -tlnp 2>/dev/null | grep ":${TEST_PORT} " | grep -oP 'pid=\K[0-9]+') + for p in $pids; do + kill "$p" 2>/dev/null + done + fi + rm -f "$NC_RECV_FILE" +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # ss -tlnp验证LISTEN端口 + local listen_cnt + listen_cnt=$(ss -tlnp 2>/dev/null | grep -c "LISTEN") + if [ "$listen_cnt" -gt 0 ] 2>/dev/null; then + tst_res $TPASS "ss -tlnp显示${listen_cnt}个LISTEN端口" + else + tst_res $TFAIL "ss -tlnp未发现LISTEN端口" + fi + ;; + 1) + # netstat -tlnp验证LISTEN端口 + if ! which netstat >/dev/null 2>&1; then + tst_res $TCONF "netstat不可用,跳过" + return + fi + local listen_cnt + listen_cnt=$(netstat -tlnp 2>/dev/null | grep -c "LISTEN") + if [ "$listen_cnt" -gt 0 ] 2>/dev/null; then + tst_res $TPASS "netstat -tlnp显示${listen_cnt}个LISTEN端口" + else + tst_res $TFAIL "netstat -tlnp未发现LISTEN端口" + fi + ;; + 2) + # nc创建TCP监听,验证ss可见 + nc -l -k "$TEST_PORT" > "$NC_RECV_FILE" 2>/dev/null & + NC_PID=$! + sleep 2 + if ! kill -0 "$NC_PID" 2>/dev/null; then + tst_res $TFAIL "nc监听端口${TEST_PORT}启动失败" + NC_PID="" + return + fi + if ss -tlnp 2>/dev/null | grep -q ":${TEST_PORT} "; then + tst_res $TPASS "nc监听端口${TEST_PORT},ss可查到LISTEN" + else + tst_res $TFAIL "nc监听端口${TEST_PORT},但ss未查到" + fi + ;; + 3) + # TCP数据收发验证 + if [ -z "$NC_PID" ] || ! kill -0 "$NC_PID" 2>/dev/null; then + tst_res $TCONF "nc监听未运行,跳过数据收发测试" + return + fi + local test_data="SMOKE_TEST_TCP_$$" + echo "$test_data" | nc -w 3 127.0.0.1 "$TEST_PORT" 2>/dev/null + sleep 2 + if grep -q "$test_data" "$NC_RECV_FILE" 2>/dev/null; then + tst_res $TPASS "TCP数据收发成功: 发送并接收到'${test_data}'" + else + tst_res $TFAIL "TCP数据收发失败: 未在服务端收到数据" + fi + ;; + 4) + # 清理nc,验证端口释放 + if [ -n "$NC_PID" ]; then + kill "$NC_PID" 2>/dev/null + NC_PID="" + fi + sleep 2 + if ss -tlnp 2>/dev/null | grep -q ":${TEST_PORT} "; then + tst_res $TFAIL "nc进程结束后端口${TEST_PORT}仍在LISTEN" + else + tst_res $TPASS "nc进程结束后端口${TEST_PORT}已释放" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/Makefile b/os_smoke_src/testcases/baseos/security/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/baseos/security/acl_access_control_tc003.sh b/os_smoke_src/testcases/baseos/security/acl_access_control_tc003.sh new file mode 100755 index 0000000..1ce4d69 --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/acl_access_control_tc003.sh @@ -0,0 +1,129 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: acl_access_control_tc003 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: ACL细粒度访问控制验证 - 验证setfacl/getfacl对单用户级别的访问授权能力 +# 前置条件: 系统具备root权限,文件系统支持ACL +# 步骤: +# 1. 创建测试用户user1和user2 +# 2. 创建user1属主文件(0700),验证user2无法读写 +# 3. user1通过setfacl授权user2读写权限 +# 4. 验证user2获得ACL授权后可读写文件 +# 5. 用getfacl验证ACL条目正确,然后移除ACL验证权限回收 +# 预期结果: +# 1. 两个测试用户创建成功 +# 2. user2无法访问user1的0700文件 +# 3. setfacl授权成功 +# 4. user2可读写被授权的文件 +# 5. getfacl显示正确ACL条目,移除后user2再次被拒绝 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="ACL细粒度访问控制验证 - 验证setfacl/getfacl对单用户级别的访问授权能力" +tcnt=5 + +TEST_USER1="acl_tst_u1_$$" +TEST_USER2="acl_tst_u2_$$" +TEST_FILE="/tmp/acl_test_$$.txt" + +setup() +{ + ensure_command "setfacl" "acl" + useradd -M "$TEST_USER1" 2>/dev/null + if [ $? -ne 0 ]; then + tst_brk $TCONF "创建测试用户${TEST_USER1}失败" + fi + useradd -M "$TEST_USER2" 2>/dev/null + if [ $? -ne 0 ]; then + userdel -rf "$TEST_USER1" 2>/dev/null + tst_brk $TCONF "创建测试用户${TEST_USER2}失败" + fi +} + +cleanup() +{ + rm -f "$TEST_FILE" + userdel -rf "$TEST_USER1" 2>/dev/null + userdel -rf "$TEST_USER2" 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 验证用户创建成功 + if id "$TEST_USER1" >/dev/null 2>&1 && id "$TEST_USER2" >/dev/null 2>&1; then + tst_res $TPASS "测试用户${TEST_USER1}和${TEST_USER2}创建成功" + else + tst_res $TFAIL "测试用户创建失败" + fi + ;; + 1) + # 创建user1属主文件(0700),验证user2无法读写 + echo "acl_test_data" > "$TEST_FILE" + chown "$TEST_USER1":"$TEST_USER1" "$TEST_FILE" + chmod 0700 "$TEST_FILE" + # 验证user2无法读取 + runuser -l "$TEST_USER2" -c "cat $TEST_FILE" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TPASS "user2无法读取user1的0700文件(权限拒绝)" + else + tst_res $TFAIL "user2竟然能读取user1的0700文件" + fi + ;; + 2) + # setfacl授权user2读写 + setfacl -m u:"$TEST_USER2":rw "$TEST_FILE" 2>/dev/null + if [ $? -eq 0 ]; then + tst_res $TPASS "setfacl授权${TEST_USER2}读写${TEST_FILE}成功" + else + tst_res $TFAIL "setfacl授权失败" + fi + ;; + 3) + # 验证user2获得授权后可读写 + local read_out + read_out=$(runuser -l "$TEST_USER2" -c "cat $TEST_FILE" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "user2获ACL授权后仍无法读取文件" + return + fi + runuser -l "$TEST_USER2" -c "echo 'acl_write_test' >> $TEST_FILE" 2>/dev/null + if [ $? -eq 0 ]; then + tst_res $TPASS "user2获ACL授权后可读写文件" + else + tst_res $TFAIL "user2获ACL授权后可读但无法写入" + fi + ;; + 4) + # getfacl验证ACL条目,然后移除ACL验证权限回收 + local facl_out + facl_out=$(getfacl "$TEST_FILE" 2>/dev/null) + if ! echo "$facl_out" | grep -q "user:${TEST_USER2}:rw"; then + tst_res $TFAIL "getfacl未显示${TEST_USER2}的rw ACL条目" + return + fi + # 移除user2的ACL + setfacl -x u:"$TEST_USER2" "$TEST_FILE" 2>/dev/null + # 验证权限回收 + runuser -l "$TEST_USER2" -c "cat $TEST_FILE" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TPASS "ACL移除后user2再次被拒绝访问" + else + tst_res $TFAIL "ACL移除后user2仍能访问文件" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/audit_file_ops_tc002.sh b/os_smoke_src/testcases/baseos/security/audit_file_ops_tc002.sh new file mode 100755 index 0000000..88d1831 --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/audit_file_ops_tc002.sh @@ -0,0 +1,150 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: audit_file_ops_tc002 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 文件操作审计记录验证 - 验证audit能正确记录root和普通用户的文件操作及用户管理操作 +# 前置条件: 系统支持yum包管理器,具备root权限 +# 步骤: +# 1. 安装audit并启动auditd服务,创建测试用户和测试目录,设置目录审计规则 +# 2. root用户执行ls/touch/rm文件操作,检查审计日志中是否记录对应操作 +# 3. 普通用户执行ls/touch/rm文件操作,检查审计日志中是否记录该用户的操作 +# 4. 删除测试用户,检查审计日志中是否记录userdel操作 +# 预期结果: +# 1. auditd服务运行正常,测试用户和审计规则设置成功 +# 2. 审计日志中包含root用户的ls、touch、rm操作记录 +# 3. 审计日志中包含普通用户uid对应的操作记录 +# 4. 审计日志中包含userdel删除用户的操作记录 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="文件操作审计记录验证 - 验证audit能正确记录root和普通用户的文件操作及用户管理操作" +tcnt=4 + +TEST_USER="audit_ops_user" +TEST_DIR="/home/audit_ops_test" + +wait_for_audit_log() +{ + local pattern="$1" + local max_wait=30 + local i=0 + while [ $i -lt $max_wait ]; do + if ausearch -k audit_ops_test -ts recent 2>/dev/null | grep -qE "$pattern"; then + return 0 + fi + sleep 1 + i=$((i + 1)) + done + return 1 +} + +setup() +{ + install_packages "audit" + systemctl start auditd + sleep 3 + if ! systemctl is-active --quiet auditd; then + systemctl status auditd 2>&1 || true + tst_brk $TFAIL "auditd服务无法启动" + fi + + auditctl -D >/dev/null 2>&1 + sleep 1 +} + +cleanup() +{ + auditctl -W "$TEST_DIR" -p wrax -k audit_ops_test 2>/dev/null + rm -rf "$TEST_DIR" 2>/dev/null + userdel -r -f "$TEST_USER" 2>/dev/null + [ -f /etc/userlog ] && sed -i "/${TEST_USER}/d" /etc/userlog 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 创建测试用户、测试目录并设置审计规则 + useradd -M "$TEST_USER" 2>/dev/null + if ! id "$TEST_USER" >/dev/null 2>&1; then + tst_res $TFAIL "创建测试用户 $TEST_USER 失败" + return + fi + + mkdir -p "$TEST_DIR" + chmod 777 "$TEST_DIR" + auditctl -w "$TEST_DIR" -p wrax -k audit_ops_test + if [ $? -ne 0 ]; then + tst_res $TFAIL "设置目录审计规则失败" + return + fi + tst_res $TPASS "测试环境和审计规则设置成功" + ;; + 1) + # root用户执行文件操作并检查审计日志 + ls "$TEST_DIR" >/dev/null 2>&1 + touch "$TEST_DIR/hello" + rm -f "$TEST_DIR/hello" + sleep 5 + + local fail=0 + if ! wait_for_audit_log 'comm="ls"'; then + tst_res $TFAIL "未找到root用户ls操作审计记录" + fail=1 + fi + if ! wait_for_audit_log 'comm="touch"'; then + tst_res $TFAIL "未找到root用户touch操作审计记录" + fail=1 + fi + if ! wait_for_audit_log 'comm="rm"'; then + tst_res $TFAIL "未找到root用户rm操作审计记录" + fail=1 + fi + [ $fail -eq 0 ] && tst_res $TPASS "root用户文件操作审计日志验证通过" + ;; + 2) + # 普通用户执行文件操作并检查审计日志 + su -c "ls $TEST_DIR/" "$TEST_USER" >/dev/null 2>&1 + su -c "touch $TEST_DIR/hello_x" "$TEST_USER" + su -c "rm -f $TEST_DIR/hello_x" "$TEST_USER" + sleep 5 + + local fail=0 + local uid + uid=$(id -u "$TEST_USER" 2>/dev/null) + if ! wait_for_audit_log "uid=$uid"; then + tst_res $TFAIL "未找到普通用户(${TEST_USER} uid=${uid})的审计记录" + fail=1 + fi + if ! wait_for_audit_log "hello_x"; then + tst_res $TFAIL "未找到普通用户文件hello_x的审计记录" + fail=1 + fi + [ $fail -eq 0 ] && tst_res $TPASS "普通用户文件操作审计日志验证通过" + ;; + 3) + # 删除测试用户并检查审计日志 + userdel -r -f "$TEST_USER" 2>/dev/null + sleep 5 + + if ausearch -ts recent 2>/dev/null | grep -qE "userdel.*${TEST_USER}|acct=\"${TEST_USER}\".*exe=.*userdel"; then + tst_res $TPASS "用户删除操作审计日志验证通过" + else + tst_res $TFAIL "未找到用户删除操作的审计记录" + fi + [ -f /etc/userlog ] && sed -i "/${TEST_USER}/d" /etc/userlog 2>/dev/null + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/audit_identity_mac_tc004.sh b/os_smoke_src/testcases/baseos/security/audit_identity_mac_tc004.sh new file mode 100755 index 0000000..7c7a43a --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/audit_identity_mac_tc004.sh @@ -0,0 +1,151 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: audit_identity_mac_tc004 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 身份鉴别与强制访问控制审计验证 - 验证SSH登录成功/失败和SELinux MAC访问的审计日志 +# 前置条件: 系统支持yum包管理器,具备root权限,SELinux非Disabled状态 +# 步骤: +# 1. 安装audit和sshpass,启动auditd和sshd服务,检查SELinux状态 +# 2. 创建测试用户并设置密码,使用正确密码SSH登录,检查审计日志中的登录成功记录 +# 3. 使用错误密码SSH登录,检查审计日志中的登录失败记录 +# 4. 使用runcon以guest_t上下文访问audit.rules触发MAC拒绝,检查审计日志中的AVC记录 +# 预期结果: +# 1. auditd和sshd服务正常运行,SELinux为Enforcing或Permissive +# 2. 审计日志中包含res=success的sshd认证记录 +# 3. 审计日志中包含res=failed的sshd认证记录 +# 4. 审计日志中包含type=AVC的强制访问控制拒绝记录 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="身份鉴别与强制访问控制审计验证 - 验证SSH登录成功/失败和SELinux MAC访问的审计日志" +tcnt=4 + +TEST_USER="audit_id_user" +TEST_PASSWD="Isd@cloud09" +SSH_PORT=22 + +setup() +{ + install_packages "audit sshpass" + systemctl start auditd + sleep 3 + if ! systemctl is-active --quiet auditd; then + systemctl status auditd 2>&1 || true + tst_brk $TFAIL "auditd服务无法启动" + fi + + if ! systemctl is-active --quiet sshd; then + systemctl start sshd 2>/dev/null + sleep 2 + fi + if ! systemctl is-active --quiet sshd; then + tst_brk $TCONF "sshd服务无法启动" + fi + + # 获取实际SSH端口 + local port + port=$(ss -tlnp | grep sshd | awk '{print $4}' | grep -oE '[0-9]+$' | head -1) + [ -n "$port" ] && SSH_PORT=$port + + auditctl -D >/dev/null 2>&1 + sleep 1 +} + +cleanup() +{ + userdel -r -f "$TEST_USER" 2>/dev/null + [ -f /etc/userlog ] && sed -i "/${TEST_USER}/d" /etc/userlog 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 检查SELinux状态 + local selinux + selinux=$(getenforce 2>/dev/null) + if [ "$selinux" = "Disabled" ]; then + tst_brk $TCONF "SELinux处于Disabled状态,无法测试MAC审计" + fi + + # 创建测试用户并设置密码 + useradd -M "$TEST_USER" 2>/dev/null + echo "$TEST_PASSWD" | passwd "$TEST_USER" --stdin 2>/dev/null + if ! id "$TEST_USER" >/dev/null 2>&1; then + tst_res $TFAIL "创建测试用户 $TEST_USER 失败" + return + fi + + # 清空secure日志和audit日志便于后续检查 + echo > /var/log/secure 2>/dev/null + echo > /var/log/audit/audit.log 2>/dev/null + + tst_res $TPASS "服务运行正常,SELinux状态: $selinux" + ;; + 1) + # 使用正确密码SSH登录 + timeout --foreground 10 sshpass -p "$TEST_PASSWD" \ + ssh -o StrictHostKeyChecking=no -p "$SSH_PORT" \ + "$TEST_USER@localhost" "whoami" 2>/dev/null + sleep 5 + + local fail=0 + cat /var/log/audit/audit.log | grep "$TEST_USER" | grep sshd | grep -q "res=success" + if [ $? -ne 0 ]; then + tst_res $TFAIL "审计日志中未找到SSH登录成功记录(res=success)" + fail=1 + fi + grep -q "Accepted password for $TEST_USER" /var/log/secure + if [ $? -ne 0 ]; then + tst_res $TFAIL "secure日志中未找到Accepted password记录" + fail=1 + fi + [ $fail -eq 0 ] && tst_res $TPASS "正确密码SSH登录审计日志验证通过" + ;; + 2) + # 使用错误密码SSH登录 + timeout --foreground 10 sshpass -p "wrong_password" \ + ssh -o StrictHostKeyChecking=no -p "$SSH_PORT" \ + "$TEST_USER@localhost" "whoami" 2>/dev/null + sleep 5 + + local fail=0 + cat /var/log/audit/audit.log | grep "$TEST_USER" | grep sshd | grep -q "res=failed" + if [ $? -ne 0 ]; then + tst_res $TFAIL "审计日志中未找到SSH登录失败记录(res=failed)" + fail=1 + fi + grep -q "Failed password for $TEST_USER" /var/log/secure + if [ $? -ne 0 ]; then + tst_res $TFAIL "secure日志中未找到Failed password记录" + fail=1 + fi + [ $fail -eq 0 ] && tst_res $TPASS "错误密码SSH登录审计日志验证通过" + ;; + 3) + # 使用runcon以guest_t上下文访问audit.rules触发MAC拒绝 + echo > /var/log/audit/audit.log 2>/dev/null + runcon -t guest_t -u guest_u -r guest_r -l s0 -- cat /etc/audit/audit.rules 2>/dev/null + sleep 3 + + grep -q 'type=AVC' /var/log/audit/audit.log + if [ $? -ne 0 ]; then + tst_res $TFAIL "审计日志中未找到type=AVC强制访问控制记录" + return + fi + tst_res $TPASS "SELinux强制访问控制审计日志验证通过" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/audit_log_verify_tc003.sh b/os_smoke_src/testcases/baseos/security/audit_log_verify_tc003.sh new file mode 100755 index 0000000..18c7a40 --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/audit_log_verify_tc003.sh @@ -0,0 +1,159 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: audit_log_verify_tc003 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 审计日志格式验证与搜索导出 - 验证审计日志字段完整性及ausearch搜索导出功能 +# 前置条件: 系统支持yum包管理器,具备root权限 +# 步骤: +# 1. 安装audit并启动auditd,创建测试用户和受限文件,设置文件审计规则 +# 2. 普通用户访问受限文件触发DAC拒绝事件,检查审计日志中的事件类型(type=)字段 +# 3. 检查审计日志中的时间戳和用户ID(uid=)字段 +# 4. 检查审计日志中的操作结果(success=no/res=failed)字段 +# 5. 使用ausearch按标签搜索审计日志 +# 6. 使用ausearch导出审计日志到文件并验证文件内容 +# 预期结果: +# 1. auditd服务运行正常,审计规则设置成功 +# 2. 审计日志中包含type=字段 +# 3. 审计日志中包含时间戳和uid=字段 +# 4. 审计日志中包含success=no或res=failed字段 +# 5. ausearch按标签能搜索到对应审计事件 +# 6. 导出文件存在且包含对应标签的审计事件 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="审计日志格式验证与搜索导出 - 验证审计日志字段完整性及ausearch搜索导出功能" +tcnt=6 + +TEST_USER="audit_fmt_user" +TEST_FILE="/etc/audit_fmt_testfile" +EXPORT_LOG="/tmp/audit_export_test.log" + +setup() +{ + install_packages "audit" + systemctl start auditd + sleep 3 + + local i=0 + while [ $i -lt 20 ]; do + if systemctl is-active --quiet auditd && auditctl -l >/dev/null 2>&1; then + break + fi + sleep 1 + i=$((i + 1)) + done + + if ! systemctl is-active --quiet auditd; then + systemctl status auditd 2>&1 || true + tst_brk $TFAIL "auditd服务无法启动" + fi + + auditctl -D >/dev/null 2>&1 + sleep 1 +} + +cleanup() +{ + auditctl -W "$TEST_FILE" -k fmt_test 2>/dev/null + userdel -r "$TEST_USER" 2>/dev/null + rm -f "$TEST_FILE" "$EXPORT_LOG" 2>/dev/null + [ -f /etc/userlog ] && sed -i "/${TEST_USER}/d" /etc/userlog 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 创建测试用户和受限文件,设置审计规则,触发DAC拒绝事件 + useradd -M "$TEST_USER" 2>/dev/null + echo "Isd@cloud09" | passwd "$TEST_USER" --stdin 2>/dev/null + touch "$TEST_FILE" + chown root:root "$TEST_FILE" + chmod 600 "$TEST_FILE" + auditctl -w "$TEST_FILE" -p rwxa -k fmt_test + if [ $? -ne 0 ]; then + tst_res $TFAIL "设置审计规则失败" + return + fi + sleep 2 + auditctl -l | grep -q "fmt_test" + if [ $? -ne 0 ]; then + tst_res $TFAIL "审计规则未生效" + return + fi + # 触发审计事件:普通用户访问受限文件 + su -s /bin/sh "$TEST_USER" -c "cat $TEST_FILE 2>/dev/null || true" + sleep 8 + service auditd reload 2>/dev/null + sleep 2 + tst_res $TPASS "审计规则设置成功并触发DAC事件" + ;; + 1) + # 检查审计日志中是否包含事件类型字段 + ausearch -k fmt_test -ts recent 2>/dev/null | grep -qE '^type=|type=' + if [ $? -ne 0 ]; then + tst_res $TFAIL "审计日志中未找到事件类型(type=)字段" + return + fi + tst_res $TPASS "审计日志包含事件类型字段" + ;; + 2) + # 检查审计日志中是否包含时间戳和用户ID字段 + local fail=0 + ausearch -k fmt_test -ts recent 2>/dev/null | grep -qE '[0-9]{10}\.[0-9]+:[0-9]+' + if [ $? -ne 0 ]; then + tst_res $TFAIL "审计日志中未找到时间戳字段" + fail=1 + fi + ausearch -k fmt_test -ts recent 2>/dev/null | grep -qE 'uid=' + if [ $? -ne 0 ]; then + tst_res $TFAIL "审计日志中未找到用户ID(uid=)字段" + fail=1 + fi + [ $fail -eq 0 ] && tst_res $TPASS "审计日志时间戳和用户ID字段验证通过" + ;; + 3) + # 检查审计日志中是否包含操作失败结果字段 + if ausearch -k fmt_test -ts recent 2>/dev/null | grep -qE 'success=no|res=failed'; then + tst_res $TPASS "审计日志操作结果字段验证通过" + else + tst_res $TFAIL "审计日志中未找到失败结果字段(success=no/res=failed)" + fi + ;; + 4) + # 使用ausearch按标签搜索审计日志 + ausearch -k fmt_test -ts recent 2>/dev/null | grep -q fmt_test + if [ $? -ne 0 ]; then + tst_res $TFAIL "ausearch按标签搜索审计日志失败" + return + fi + tst_res $TPASS "ausearch按标签搜索审计日志成功" + ;; + 5) + # 导出审计日志到文件并验证内容 + ausearch -m all -ts today -i > "$EXPORT_LOG" 2>/dev/null + if [ ! -f "$EXPORT_LOG" ] || [ ! -s "$EXPORT_LOG" ]; then + tst_res $TFAIL "审计日志导出失败,文件不存在或为空" + return + fi + grep -q fmt_test "$EXPORT_LOG" + if [ $? -ne 0 ]; then + tst_res $TFAIL "导出文件中未找到fmt_test标签的审计事件" + return + fi + tst_res $TPASS "审计日志导出和内容验证通过" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/audit_service_tc001.sh b/os_smoke_src/testcases/baseos/security/audit_service_tc001.sh new file mode 100755 index 0000000..3f7820a --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/audit_service_tc001.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: audit_service_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 审计服务基础功能验证 - 验证auditd服务管理和auditctl规则增删查操作 +# 前置条件: 系统支持yum包管理器,具备root权限 +# 步骤: +# 1. 安装audit软件包并启动auditd服务,检查服务状态 +# 2. 使用auditctl -s查看审计子系统状态 +# 3. 使用auditctl -l查看当前审计规则列表 +# 4. 添加文件监控审计规则并验证规则生效 +# 5. 添加系统调用审计规则并验证规则生效 +# 6. 删除已添加的审计规则并验证规则已清除 +# 预期结果: +# 1. auditd服务状态为active(running) +# 2. auditctl -s正常返回审计状态信息 +# 3. auditctl -l正常返回审计规则列表 +# 4. 文件监控规则添加成功且可查询到 +# 5. 系统调用审计规则添加成功且可查询到 +# 6. 规则删除后auditctl -l中不再包含对应规则 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="审计服务基础功能验证 - 验证auditd服务管理和auditctl规则增删查操作" +tcnt=6 + +TEST_FILE="/tmp/audit_svc_test_file" + +setup() +{ + install_packages "audit" + systemctl start auditd + sleep 3 + if ! systemctl is-active --quiet auditd; then + systemctl status auditd 2>&1 || true + tst_brk $TFAIL "auditd服务无法启动" + fi + + auditctl -D >/dev/null 2>&1 + sleep 1 + touch "$TEST_FILE" +} + +cleanup() +{ + auditctl -W "$TEST_FILE" -p rwxa -k svc_test 2>/dev/null + auditctl -d always,exit -F arch=b64 -S openat -F exit=-EACCES -k svc_syscall_test 2>/dev/null + rm -f "$TEST_FILE" 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 检查auditd服务是否正常运行 + systemctl --no-pager status auditd | grep -q "active (running)" + if [ $? -ne 0 ]; then + tst_res $TFAIL "auditd服务未运行" + return + fi + tst_res $TPASS "auditd服务启动成功" + ;; + 1) + # 查看审计子系统状态 + local output + output=$(auditctl -s 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "auditctl -s 执行失败" + return + fi + echo "$output" | grep -q "enabled" + if [ $? -ne 0 ]; then + tst_res $TFAIL "auditctl -s 未返回enabled字段" + return + fi + tst_res $TPASS "auditctl -s 查看审计状态正常" + ;; + 2) + # 查看当前审计规则列表 + auditctl -l >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "auditctl -l 执行失败" + return + fi + tst_res $TPASS "auditctl -l 查看审计规则正常" + ;; + 3) + # 添加文件监控审计规则并验证 + auditctl -w "$TEST_FILE" -p rwxa -k svc_test + if [ $? -ne 0 ]; then + tst_res $TFAIL "添加文件监控审计规则失败" + return + fi + sleep 1 + auditctl -l | grep -q "svc_test" + if [ $? -ne 0 ]; then + tst_res $TFAIL "文件监控审计规则未在规则列表中生效" + return + fi + tst_res $TPASS "文件监控审计规则添加并验证成功" + ;; + 4) + # 添加系统调用审计规则并验证 + auditctl -a always,exit -F arch=b64 -S openat -F exit=-EACCES -k svc_syscall_test + if [ $? -ne 0 ]; then + tst_res $TFAIL "添加系统调用审计规则失败" + return + fi + sleep 1 + auditctl -l | grep -q "svc_syscall_test" + if [ $? -ne 0 ]; then + tst_res $TFAIL "系统调用审计规则未在规则列表中生效" + return + fi + tst_res $TPASS "系统调用审计规则添加并验证成功" + ;; + 5) + # 删除审计规则并验证 + auditctl -W "$TEST_FILE" -p rwxa -k svc_test 2>/dev/null + auditctl -d always,exit -F arch=b64 -S openat -F exit=-EACCES -k svc_syscall_test 2>/dev/null + sleep 1 + if auditctl -l | grep -qE "svc_test|svc_syscall_test"; then + tst_res $TFAIL "审计规则删除后仍存在" + return + fi + tst_res $TPASS "审计规则删除并验证成功" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/dac_access_control_tc002.sh b/os_smoke_src/testcases/baseos/security/dac_access_control_tc002.sh new file mode 100755 index 0000000..670e811 --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/dac_access_control_tc002.sh @@ -0,0 +1,126 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: dac_access_control_tc002 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: DAC自主访问控制验证 - 验证文件属主权限控制及非授权用户访问拒绝 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 创建测试用户user1和user2 +# 2. user1创建文件并设置权限0600,验证user1可读写 +# 3. 验证user2无法写入user1的0600权限文件 +# 4. user1设置文件权限0644,验证user2可读但不可写 +# 5. user1删除文件验证属主删除权限 +# 预期结果: +# 1. 两个测试用户创建成功 +# 2. user1可正常读写自己的文件 +# 3. user2写入被拒绝 +# 4. user2可读不可写 +# 5. user1可删除自己的文件 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="DAC自主访问控制验证 - 验证文件属主权限控制及非授权用户访问拒绝" +tcnt=5 + +TEST_USER1="dac_tst_u1_$$" +TEST_USER2="dac_tst_u2_$$" +TEST_FILE="/tmp/dac_test_$$.txt" + +setup() +{ + useradd -M "$TEST_USER1" 2>/dev/null + if [ $? -ne 0 ]; then + tst_brk $TCONF "创建测试用户${TEST_USER1}失败" + fi + useradd -M "$TEST_USER2" 2>/dev/null + if [ $? -ne 0 ]; then + userdel -rf "$TEST_USER1" 2>/dev/null + tst_brk $TCONF "创建测试用户${TEST_USER2}失败" + fi +} + +cleanup() +{ + rm -f "$TEST_FILE" + userdel -rf "$TEST_USER1" 2>/dev/null + userdel -rf "$TEST_USER2" 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 验证用户创建成功 + if id "$TEST_USER1" >/dev/null 2>&1 && id "$TEST_USER2" >/dev/null 2>&1; then + tst_res $TPASS "测试用户${TEST_USER1}和${TEST_USER2}创建成功" + else + tst_res $TFAIL "测试用户创建失败" + fi + ;; + 1) + # user1创建文件并设置0600,验证可读写 + runuser -l "$TEST_USER1" -c "echo 'dac_test_data' > $TEST_FILE" 2>/dev/null + if [ $? -ne 0 ]; then + # /tmp有sticky bit,用root创建再chown + echo "dac_test_data" > "$TEST_FILE" + chown "$TEST_USER1":"$TEST_USER1" "$TEST_FILE" + fi + chmod 0600 "$TEST_FILE" + # 验证user1可读 + local read_out + read_out=$(runuser -l "$TEST_USER1" -c "cat $TEST_FILE" 2>/dev/null) + if [ $? -eq 0 ] && echo "$read_out" | grep -q "dac_test_data"; then + tst_res $TPASS "user1可读写自有文件(权限0600)" + else + tst_res $TFAIL "user1无法读取自有文件" + fi + ;; + 2) + # 验证user2无法写入user1的0600权限文件 + runuser -l "$TEST_USER2" -c "echo 'hack' > $TEST_FILE" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TPASS "user2无法写入user1的0600文件(权限拒绝)" + else + tst_res $TFAIL "user2竟然能写入user1的0600文件" + fi + ;; + 3) + # user1设置0644,验证user2可读不可写 + chmod 0644 "$TEST_FILE" + local read_out + read_out=$(runuser -l "$TEST_USER2" -c "cat $TEST_FILE" 2>/dev/null) + if [ $? -ne 0 ]; then + tst_res $TFAIL "user2无法读取0644文件" + return + fi + runuser -l "$TEST_USER2" -c "echo 'hack' > $TEST_FILE" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TPASS "0644权限: user2可读但不可写" + else + tst_res $TFAIL "0644权限: user2竟然能写入" + fi + ;; + 4) + # user1删除自有文件 + # 文件在/tmp下,有sticky bit,属主可删 + rm -f "$TEST_FILE" 2>/dev/null + if [ ! -f "$TEST_FILE" ]; then + tst_res $TPASS "文件删除成功" + else + tst_res $TFAIL "文件删除失败" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/firewall_port_access_tc003.sh b/os_smoke_src/testcases/baseos/security/firewall_port_access_tc003.sh new file mode 100755 index 0000000..3c577ee --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/firewall_port_access_tc003.sh @@ -0,0 +1,130 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: firewall_port_access_tc003 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 端口与访问控制端到端验证 - 通过iptables封禁端口、IP白名单、IP黑名单及服务停止场景验证实际访问效果 +# 前置条件: 系统具备root权限,可安装nginx +# 步骤: +# 1. 安装并启动nginx服务,curl验证80端口可正常访问 +# 2. iptables添加DROP规则关闭80端口,验证无法访问 +# 3. iptables插入规则放开本机IP访问80端口,验证可访问 +# 4. 清空iptables规则,验证恢复可访问 +# 5. iptables添加规则限制本机IP所有访问,验证无法访问 +# 6. 清空iptables规则,验证恢复可访问 +# 7. 停止nginx服务,验证无法访问 +# 预期结果: +# 1. nginx启动成功,curl返回200 +# 2. 80端口被封后curl超时或被拒 +# 3. 放开本机IP后curl返回200 +# 4. 清空规则后curl返回200 +# 5. 限制本机IP后curl超时或被拒 +# 6. 清空规则后curl返回200 +# 7. nginx停止后curl连接被拒 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="端口与访问控制端到端验证 - 通过iptables封禁端口、IP白名单、IP黑名单及服务停止场景验证实际访问效果" +tcnt=7 + +LOCAL_IP="127.0.0.1" + +setup() +{ + install_packages "nginx iptables curl" + iptables -F 2>/dev/null + systemctl stop nginx 2>/dev/null +} + +cleanup() +{ + iptables -F 2>/dev/null + systemctl stop nginx 2>/dev/null +} + +_curl_ok() +{ + curl -s -o /dev/null -w "%{http_code}" http://${LOCAL_IP} --connect-timeout 5 2>/dev/null | grep -qE "^(200|301|302)" +} + +run_test() +{ + local n=$1 + + case $n in + 0) + systemctl restart nginx 2>/dev/null + sleep 2 + if ! _curl_ok; then + tst_res $TFAIL "nginx启动后无法访问80端口" + return + fi + tst_res $TPASS "nginx启动成功,80端口可正常访问" + ;; + 1) + iptables -A INPUT -p tcp --dport 80 -j DROP + iptables -A OUTPUT -p tcp --dport 80 -j DROP + sleep 1 + if _curl_ok; then + tst_res $TFAIL "封禁80端口后仍可访问" + return + fi + tst_res $TPASS "iptables封禁80端口后无法访问" + ;; + 2) + iptables -I INPUT -s $LOCAL_IP -p tcp --dport 80 -j ACCEPT + iptables -I OUTPUT -s $LOCAL_IP -p tcp --dport 80 -j ACCEPT + sleep 1 + if ! _curl_ok; then + tst_res $TFAIL "放开本机IP后仍无法访问" + return + fi + tst_res $TPASS "放开本机IP后可正常访问80端口" + ;; + 3) + iptables -F + sleep 1 + if ! _curl_ok; then + tst_res $TFAIL "清空规则后仍无法访问" + return + fi + tst_res $TPASS "清空iptables规则后恢复访问" + ;; + 4) + iptables -A INPUT -s $LOCAL_IP -j DROP + sleep 1 + if _curl_ok; then + tst_res $TFAIL "限制本机IP后仍可访问" + return + fi + tst_res $TPASS "iptables限制本机IP后无法访问" + ;; + 5) + iptables -F + sleep 1 + if ! _curl_ok; then + tst_res $TFAIL "再次清空规则后仍无法访问" + return + fi + tst_res $TPASS "再次清空iptables规则后恢复访问" + ;; + 6) + systemctl stop nginx 2>/dev/null + sleep 2 + if _curl_ok; then + tst_res $TFAIL "nginx停止后仍可访问" + return + fi + tst_res $TPASS "nginx停止后无法访问" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/firewalld_service_tc001.sh b/os_smoke_src/testcases/baseos/security/firewalld_service_tc001.sh new file mode 100755 index 0000000..1261df7 --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/firewalld_service_tc001.sh @@ -0,0 +1,120 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: firewalld_service_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: firewalld服务管理验证 - 启动/停止/重启服务及firewall-cmd基础操作 +# 前置条件: 系统支持yum包管理器,具备root权限 +# 步骤: +# 1. 安装firewalld并启动服务,检查状态为running +# 2. 停止firewalld服务,检查状态为dead +# 3. 重启firewalld服务,检查状态恢复为running +# 4. 使用firewall-cmd --state检查防火墙运行状态 +# 5. 使用firewall-cmd查看默认zone +# 6. 使用firewall-cmd列出当前zone已开放的服务和端口 +# 预期结果: +# 1. 启动后服务状态为running +# 2. 停止后服务状态为dead +# 3. 重启后服务状态为running +# 4. firewall-cmd --state返回running +# 5. 能正常获取默认zone名称 +# 6. 能正常列出已开放服务和端口列表 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="firewalld服务管理验证 - 启动/停止/重启服务及firewall-cmd基础操作" +tcnt=6 + +PKG_INSTALLED=0 + +setup() +{ + if ! rpm -q firewalld >/dev/null 2>&1; then + install_packages "firewalld" + PKG_INSTALLED=1 + fi +} + +cleanup() +{ + systemctl stop firewalld 2>/dev/null + if [ $PKG_INSTALLED -eq 1 ]; then + remove_packages "firewalld" + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + systemctl start firewalld 2>/dev/null + sleep 1 + if ! systemctl status firewalld 2>/dev/null | grep -q "running"; then + tst_res $TFAIL "firewalld启动后状态不是running" + return + fi + tst_res $TPASS "firewalld启动成功,状态为running" + ;; + 1) + systemctl stop firewalld 2>/dev/null + sleep 1 + if ! systemctl status firewalld 2>/dev/null | grep -q "dead"; then + tst_res $TFAIL "firewalld停止后状态不是dead" + return + fi + tst_res $TPASS "firewalld停止成功,状态为dead" + ;; + 2) + systemctl restart firewalld 2>/dev/null + sleep 1 + if ! systemctl status firewalld 2>/dev/null | grep -q "running"; then + tst_res $TFAIL "firewalld重启后状态不是running" + return + fi + tst_res $TPASS "firewalld重启成功,状态为running" + ;; + 3) + local state + state=$(firewall-cmd --state 2>&1) + if [ "$state" != "running" ]; then + tst_res $TFAIL "firewall-cmd --state返回'$state',期望'running'" + return + fi + tst_res $TPASS "firewall-cmd --state返回running" + ;; + 4) + local zone + zone=$(firewall-cmd --get-default-zone 2>&1) + if [ $? -ne 0 ] || [ -z "$zone" ]; then + tst_res $TFAIL "firewall-cmd --get-default-zone执行失败" + return + fi + tst_res $TPASS "默认zone为: $zone" + ;; + 5) + local services ports + services=$(firewall-cmd --list-services 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "firewall-cmd --list-services执行失败" + return + fi + ports=$(firewall-cmd --list-ports 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "firewall-cmd --list-ports执行失败" + return + fi + tst_res $TPASS "已开放服务: [$services], 已开放端口: [$ports]" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/firewalld_zone_manage_tc004.sh b/os_smoke_src/testcases/baseos/security/firewalld_zone_manage_tc004.sh new file mode 100755 index 0000000..0038b21 --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/firewalld_zone_manage_tc004.sh @@ -0,0 +1,157 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: firewalld_zone_manage_tc004 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: firewalld zone/service/port动态管理验证 - 验证firewall-cmd动态添加和移除服务、端口及zone切换操作 +# 前置条件: 系统支持yum包管理器,具备root权限 +# 步骤: +# 1. 安装并启动firewalld,记录默认zone和已开放服务列表 +# 2. 动态添加http服务到当前zone,验证服务已生效 +# 3. 动态移除http服务,验证服务已移除 +# 4. 动态开放8080/tcp端口,验证端口已生效 +# 5. 动态移除8080/tcp端口,验证端口已移除 +# 6. 切换默认zone为trusted,验证切换成功后恢复原zone +# 预期结果: +# 1. firewalld启动成功,能获取默认zone和服务列表 +# 2. --add-service=http成功,--list-services包含http +# 3. --remove-service=http成功,--list-services不包含http +# 4. --add-port=8080/tcp成功,--list-ports包含8080/tcp +# 5. --remove-port=8080/tcp成功,--list-ports不包含8080/tcp +# 6. --set-default-zone=trusted成功,--get-default-zone返回trusted,恢复后返回原zone +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="firewalld zone/service/port动态管理验证 - 验证firewall-cmd动态添加和移除服务、端口及zone切换操作" +tcnt=6 + +PKG_INSTALLED=0 +ORIG_ZONE="" + +setup() +{ + if ! rpm -q firewalld >/dev/null 2>&1; then + install_packages "firewalld" + PKG_INSTALLED=1 + fi + systemctl start firewalld 2>/dev/null + sleep 1 + ORIG_ZONE=$(firewall-cmd --get-default-zone 2>/dev/null) +} + +cleanup() +{ + if [ -n "$ORIG_ZONE" ]; then + firewall-cmd --set-default-zone="$ORIG_ZONE" 2>/dev/null + fi + firewall-cmd --remove-service=http 2>/dev/null + firewall-cmd --remove-port=8080/tcp 2>/dev/null + systemctl stop firewalld 2>/dev/null + if [ $PKG_INSTALLED -eq 1 ]; then + remove_packages "firewalld" + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if ! systemctl is-active firewalld >/dev/null 2>&1; then + tst_res $TFAIL "firewalld未处于运行状态" + return + fi + if [ -z "$ORIG_ZONE" ]; then + tst_res $TFAIL "无法获取默认zone" + return + fi + local services + services=$(firewall-cmd --list-services 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "firewall-cmd --list-services执行失败" + return + fi + tst_res $TPASS "firewalld运行正常,默认zone=$ORIG_ZONE,已开放服务: [$services]" + ;; + 1) + firewall-cmd --add-service=http 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "firewall-cmd --add-service=http执行失败" + return + fi + if ! firewall-cmd --list-services 2>/dev/null | grep -qw "http"; then + tst_res $TFAIL "添加http服务后--list-services中未找到http" + return + fi + tst_res $TPASS "动态添加http服务成功" + ;; + 2) + firewall-cmd --remove-service=http 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "firewall-cmd --remove-service=http执行失败" + return + fi + if firewall-cmd --list-services 2>/dev/null | grep -qw "http"; then + tst_res $TFAIL "移除http服务后--list-services中仍存在http" + return + fi + tst_res $TPASS "动态移除http服务成功" + ;; + 3) + firewall-cmd --add-port=8080/tcp 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "firewall-cmd --add-port=8080/tcp执行失败" + return + fi + if ! firewall-cmd --list-ports 2>/dev/null | grep -qw "8080/tcp"; then + tst_res $TFAIL "添加8080/tcp端口后--list-ports中未找到" + return + fi + tst_res $TPASS "动态开放8080/tcp端口成功" + ;; + 4) + firewall-cmd --remove-port=8080/tcp 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "firewall-cmd --remove-port=8080/tcp执行失败" + return + fi + if firewall-cmd --list-ports 2>/dev/null | grep -qw "8080/tcp"; then + tst_res $TFAIL "移除8080/tcp端口后--list-ports中仍存在" + return + fi + tst_res $TPASS "动态移除8080/tcp端口成功" + ;; + 5) + firewall-cmd --set-default-zone=trusted 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "firewall-cmd --set-default-zone=trusted执行失败" + return + fi + local cur_zone + cur_zone=$(firewall-cmd --get-default-zone 2>/dev/null) + if [ "$cur_zone" != "trusted" ]; then + tst_res $TFAIL "切换后默认zone为'$cur_zone',期望'trusted'" + return + fi + # 恢复原zone + firewall-cmd --set-default-zone="$ORIG_ZONE" 2>/dev/null + local restored_zone + restored_zone=$(firewall-cmd --get-default-zone 2>/dev/null) + if [ "$restored_zone" != "$ORIG_ZONE" ]; then + tst_res $TFAIL "恢复默认zone失败,当前'$restored_zone',期望'$ORIG_ZONE'" + return + fi + tst_res $TPASS "切换默认zone为trusted成功,恢复为$ORIG_ZONE成功" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/iptables_rules_tc002.sh b/os_smoke_src/testcases/baseos/security/iptables_rules_tc002.sh new file mode 100755 index 0000000..7954b86 --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/iptables_rules_tc002.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: iptables_rules_tc002 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: iptables规则管理验证 - 查看/添加/插入/删除/清空规则的完整CRUD操作 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 执行iptables -L查看当前规则列表 +# 2. 使用iptables -A追加一条ACCEPT规则并验证生效 +# 3. 使用iptables -I在链首插入一条DROP规则并验证位置正确 +# 4. 使用iptables -D删除单条规则并验证已移除 +# 5. 使用iptables -F清空所有规则并验证规则为空 +# 预期结果: +# 1. iptables -L命令执行成功,正常显示规则列表 +# 2. 追加的ACCEPT规则出现在规则列表中 +# 3. 插入的DROP规则出现在链首位置 +# 4. 被删除的规则不再出现在列表中 +# 5. 清空后INPUT链无自定义规则 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="iptables规则管理验证 - 查看/添加/插入/删除/清空规则的完整CRUD操作" +tcnt=5 + +TEST_SRC_IP="10.99.99.99" + +setup() +{ + install_packages "iptables" + iptables -F 2>/dev/null +} + +cleanup() +{ + iptables -F 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + iptables -L -n 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "iptables -L执行失败" + return + fi + tst_res $TPASS "iptables -L查看规则成功" + ;; + 1) + iptables -A INPUT -s $TEST_SRC_IP -p tcp --dport 22 -j ACCEPT + if [ $? -ne 0 ]; then + tst_res $TFAIL "iptables -A追加规则失败" + return + fi + if ! iptables -L INPUT -n | grep -q "$TEST_SRC_IP"; then + tst_res $TFAIL "追加的规则未在列表中找到" + return + fi + tst_res $TPASS "iptables -A追加ACCEPT规则成功并已生效" + ;; + 2) + iptables -I INPUT -s $TEST_SRC_IP -p tcp --dport 80 -j DROP + if [ $? -ne 0 ]; then + tst_res $TFAIL "iptables -I插入规则失败" + return + fi + local first_rule + first_rule=$(iptables -L INPUT -n --line-numbers | sed -n '3p') + if ! echo "$first_rule" | grep -q "DROP"; then + tst_res $TFAIL "插入的DROP规则未在链首位置" + return + fi + tst_res $TPASS "iptables -I插入DROP规则成功,位于链首" + ;; + 3) + iptables -D INPUT -s $TEST_SRC_IP -p tcp --dport 80 -j DROP 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "iptables -D删除规则失败" + return + fi + if iptables -L INPUT -n | grep "$TEST_SRC_IP" | grep -q "DROP.*dpt:80"; then + tst_res $TFAIL "删除后规则仍然存在" + return + fi + tst_res $TPASS "iptables -D删除单条规则成功" + ;; + 4) + iptables -F + if [ $? -ne 0 ]; then + tst_res $TFAIL "iptables -F清空规则失败" + return + fi + local rule_count + rule_count=$(iptables -L INPUT -n | tail -n +3 | wc -l) + if [ "$rule_count" -ne 0 ]; then + tst_res $TFAIL "清空后INPUT链仍有${rule_count}条规则" + return + fi + tst_res $TPASS "iptables -F清空规则成功" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/lsm_framework_tc001.sh b/os_smoke_src/testcases/baseos/security/lsm_framework_tc001.sh new file mode 100755 index 0000000..eeccfea --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/lsm_framework_tc001.sh @@ -0,0 +1,120 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: lsm_framework_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: LSM安全框架验证 - 验证/sys/kernel/security/lsm存在且各安全模块状态正确 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 检查/sys/kernel/security/lsm文件存在且可读 +# 2. 验证capability模块存在于lsm列表中(内核必备) +# 3. 验证yama模块存在于lsm列表中 +# 4. 根据getenforce判断SELinux运行状态与lsm列表的一致性 +# 预期结果: +# 1. /sys/kernel/security/lsm文件存在且内容非空 +# 2. lsm列表包含capability +# 3. lsm列表包含yama +# 4. SELinux非disabled时lsm包含selinux,disabled时不要求 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="LSM安全框架验证 - 验证/sys/kernel/security/lsm存在且各安全模块状态正确" +tcnt=4 + +LSM_FILE="/sys/kernel/security/lsm" + +setup() +{ + : +} + +cleanup() +{ + : +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 检查lsm文件存在且可读 + if [ ! -f "$LSM_FILE" ]; then + tst_res $TFAIL "${LSM_FILE}不存在" + return + fi + local lsm_content + lsm_content=$(cat "$LSM_FILE" 2>/dev/null) + if [ -z "$lsm_content" ]; then + tst_res $TFAIL "${LSM_FILE}内容为空" + return + fi + tst_res $TPASS "${LSM_FILE}存在且内容: ${lsm_content}" + ;; + 1) + # 验证capability模块(内核必备) + local lsm_content + lsm_content=$(cat "$LSM_FILE" 2>/dev/null) + if echo "$lsm_content" | grep -qi "capability"; then + tst_res $TPASS "lsm列表包含capability模块" + else + tst_res $TFAIL "lsm列表缺少capability模块: ${lsm_content}" + fi + ;; + 2) + # 验证yama模块 + local lsm_content + lsm_content=$(cat "$LSM_FILE" 2>/dev/null) + if echo "$lsm_content" | grep -qi "yama"; then + tst_res $TPASS "lsm列表包含yama模块" + else + tst_res $TCONF "lsm列表未包含yama模块(可能未编译): ${lsm_content}" + fi + ;; + 3) + # 根据SELinux运行状态判断一致性 + local lsm_content + lsm_content=$(cat "$LSM_FILE" 2>/dev/null) + local has_selinux_in_lsm=0 + echo "$lsm_content" | grep -qi "selinux" && has_selinux_in_lsm=1 + + if ! which getenforce >/dev/null 2>&1; then + # 无getenforce命令,按lsm内容判断 + if [ $has_selinux_in_lsm -eq 1 ]; then + tst_res $TPASS "lsm包含selinux(getenforce不可用,仅检查lsm)" + else + tst_res $TCONF "getenforce不可用且lsm未包含selinux,跳过SELinux检查" + fi + return + fi + + local se_status + se_status=$(getenforce 2>/dev/null) + if [ "$se_status" = "Disabled" ]; then + # SELinux disabled: 内核启动时未加载,lsm中不应有selinux + if [ $has_selinux_in_lsm -eq 0 ]; then + tst_res $TPASS "SELinux为Disabled,lsm列表中正确地不包含selinux" + else + tst_res $TPASS "SELinux为Disabled但lsm仍包含selinux(内核编译内置)" + fi + else + # SELinux为Enforcing或Permissive,lsm中必须有selinux + if [ $has_selinux_in_lsm -eq 1 ]; then + tst_res $TPASS "SELinux为${se_status},lsm列表正确包含selinux" + else + tst_res $TFAIL "SELinux为${se_status}但lsm列表缺少selinux: ${lsm_content}" + fi + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/passwd_crypt_cfg_tc001.sh b/os_smoke_src/testcases/baseos/security/passwd_crypt_cfg_tc001.sh new file mode 100755 index 0000000..f43ed5e --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/passwd_crypt_cfg_tc001.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: passwd_crypt_cfg_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 验证系统是否对用户账户密码进行加密配置 +# 前置条件: 需要 root 权限 +# 用例步骤: +# 1. 检查/etc/pam.d/system-auth配置 +# 2. 检查/etc/pam.d/password-auth配置 +# 3. 检查/etc/login.defs配置 +# 4. 创建测试用户并设置密码 +# 5. 验证密码哈希算法为SHA512 +# 预期结果: +# 1. system-auth包含sha512配置 +# 2. password-auth包含sha512配置 +# 3. login.defs中ENCRYPT_METHOD为SHA512 +# 4. 测试用户创建和密码设置成功 +# 5. /etc/shadow中密码哈希以$6$开头 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "检查 PAM sha512 配置" + "检查 login.defs 加密配置" + "创建用户并验证密码哈希为 SHA512" +) +tcnt=${#TCASE_DESC[@]} + +TEST_USER="pwtest134" + +setup() +{ + check_root || tst_brk $TCONF "需要 root 权限" + userdel -r -f "${TEST_USER}" >/dev/null 2>&1 || true + sed -i "/${TEST_USER}/d" /etc/userlog 2>/dev/null || true +} + +cleanup() +{ + userdel -r -f "${TEST_USER}" >/dev/null 2>&1 || true + sed -i "/${TEST_USER}/d" /etc/userlog 2>/dev/null || true +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 PAM 配置中的 sha512" + local fail=0 + if [ -f /etc/pam.d/system-auth ]; then + if cat /etc/pam.d/system-auth | grep password | grep sufficient | grep pam_unix | grep -q sha512; then + tst_res $TINFO "system-auth sha512 配置正确" + else + fail=1 + fi + fi + if [ -f /etc/pam.d/password-auth ]; then + if cat /etc/pam.d/password-auth | grep password | grep sufficient | grep pam_unix | grep -q sha512; then + tst_res $TINFO "password-auth sha512 配置正确" + else + fail=1 + fi + fi + if [ $fail -eq 0 ]; then + tst_res $TPASS "PAM sha512 配置检查通过" + else + tst_res $TFAIL "PAM sha512 配置检查失败" + fi + ;; + 1) + tst_res $TINFO "检查 /etc/login.defs ENCRYPT_METHOD" + if cat /etc/login.defs | grep ENCRYPT_METHOD | grep -q SHA512; then + tst_res $TPASS "/etc/login.defs ENCRYPT_METHOD 为 SHA512" + else + tst_res $TFAIL "/etc/login.defs ENCRYPT_METHOD 非 SHA512" + fi + ;; + 2) + tst_res $TINFO "创建用户并验证密码哈希" + if ! useradd -M "${TEST_USER}" 2>/dev/null; then + tst_res $TFAIL "useradd ${TEST_USER} 失败" + return + fi + echo "${TEST_USER}:9823oop!L" | chpasswd 2>/dev/null + if cat /etc/shadow | grep "${TEST_USER}" | awk -F: '{print $2}' | grep -q '\$6\$'; then + tst_res $TPASS "密码哈希为 SHA512 (\$6\$)" + else + tst_res $TFAIL "密码哈希非 SHA512" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/security/user_uid_unique_tc001.sh b/os_smoke_src/testcases/baseos/security/user_uid_unique_tc001.sh new file mode 100755 index 0000000..1236c1b --- /dev/null +++ b/os_smoke_src/testcases/baseos/security/user_uid_unique_tc001.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: user_uid_unique_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 用户标识应使用帐户名和帐户ID,在操作系统的整个生存周期内用户标识具有唯一性 +# 前置条件: 需要 root 权限 +# 用例步骤: +# 1. 添加idtest用户并获取其uid +# 2. 使用相同uid添加idtestx,预期失败 +# 3. 正常添加idtestx,验证uid不同 +# 预期结果: +# 1. 添加idtest用户成功 +# 2. 使用相同uid添加失败 +# 3. idtestx的uid与idtest不同 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "添加用户并获取 uid" + "使用相同 uid 添加用户应失败" + "正常添加用户 uid 应不同" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + check_root || tst_brk $TCONF "需要 root 权限" + userdel -r idtest >/dev/null 2>&1 || true + userdel -r idtestx >/dev/null 2>&1 || true + sed -i '/idtest/d' /etc/userlog 2>/dev/null || true +} + +cleanup() +{ + userdel -r idtest >/dev/null 2>&1 || true + userdel -r idtestx >/dev/null 2>&1 || true + sed -i '/idtest/d' /etc/userlog 2>/dev/null || true +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "添加 idtest 用户" + if useradd -M idtest 2>/dev/null; then + if id idtest | grep -q uid; then + tst_res $TPASS "idtest 用户添加成功, uid=$(id -u idtest)" + else + tst_res $TFAIL "idtest 用户添加后 id 查询失败" + fi + else + tst_res $TFAIL "useradd idtest 失败" + fi + ;; + 1) + tst_res $TINFO "使用相同 uid 添加 idtestx" + local uid + uid=$(id -u idtest 2>/dev/null) + if [ -z "${uid}" ]; then + tst_res $TFAIL "无法获取 idtest uid" + return + fi + if useradd -M idtestx -u "${uid}" 2>/dev/null; then + tst_res $TFAIL "使用相同 uid 添加用户应失败但成功了" + userdel -r idtestx >/dev/null 2>&1 || true + else + tst_res $TPASS "使用相同 uid 添加用户被拒绝" + fi + ;; + 2) + tst_res $TINFO "正常添加 idtestx 并验证 uid 不同" + useradd -M idtestx 2>/dev/null + local uid uidx + uid=$(id -u idtest 2>/dev/null) + uidx=$(id -u idtestx 2>/dev/null) + if [ -n "${uid}" ] && [ -n "${uidx}" ] && [ "${uid}" -ne "${uidx}" ]; then + tst_res $TPASS "uid 不同: idtest=${uid}, idtestx=${uidx}" + else + tst_res $TFAIL "uid 相同或获取失败: idtest=${uid}, idtestx=${uidx}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/Makefile b/os_smoke_src/testcases/baseos/service/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/baseos/service/atd_service_tc006.sh b/os_smoke_src/testcases/baseos/service/atd_service_tc006.sh new file mode 100755 index 0000000..b22cddb --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/atd_service_tc006.sh @@ -0,0 +1,157 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: atd_service_tc006 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: atd一次性定时任务服务验证 - 验证atd服务状态及at命令提交/执行/删除功能 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 验证atd服务处于运行状态 +# 2. 使用at命令提交一个延迟任务并验证队列 +# 3. 等待任务触发并验证执行结果 +# 4. 提交并删除at任务,验证atq/atrm功能 +# 预期结果: +# 1. atd服务active(running) +# 2. at命令提交成功,atq可查询到任务 +# 3. 任务在预期时间内触发,标记文件生成 +# 4. atrm可删除待执行任务 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "验证atd服务运行状态" + "at命令提交任务并验证队列" + "等待at任务触发并验证结果" + "atrm删除at任务" +) +tcnt=${#TCASE_DESC[@]} + +AT_MARKER="/tmp/atd_smoke_test_$$" +ATD_WAS_ACTIVE=0 + +setup() +{ + ensure_command "at" + + if systemctl is-active --quiet atd 2>/dev/null; then + ATD_WAS_ACTIVE=1 + fi + + if ! systemctl is-active --quiet atd 2>/dev/null; then + systemctl start atd 2>/dev/null + sleep 1 + fi + + rm -f "$AT_MARKER" +} + +cleanup() +{ + # 清理所有测试残留任务 + for job in $(atq 2>/dev/null | awk '{print $1}'); do + atrm "$job" 2>/dev/null + done + rm -f "$AT_MARKER" + + if [ $ATD_WAS_ACTIVE -eq 0 ]; then + systemctl stop atd 2>/dev/null + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if systemctl is-active --quiet atd 2>/dev/null; then + tst_res $TPASS "atd服务正在运行" + else + systemctl start atd 2>/dev/null + sleep 1 + if systemctl is-active --quiet atd 2>/dev/null; then + tst_res $TPASS "atd服务启动成功" + else + tst_res $TFAIL "atd服务未运行且无法启动" + fi + fi + ;; + 1) + ensure_command "at" + + rm -f "$AT_MARKER" + echo "touch ${AT_MARKER}" | at now + 1 minute 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "at命令提交任务失败" + return + fi + + local job_count + job_count=$(atq 2>/dev/null | wc -l) + if [ "$job_count" -gt 0 ]; then + tst_res $TPASS "at任务提交成功,队列中有${job_count}个任务" + else + tst_res $TFAIL "at任务提交后atq未查询到任务" + fi + ;; + 2) + # 提交一个"now"任务用于立即触发验证 + rm -f "$AT_MARKER" + echo "touch ${AT_MARKER}" | at now 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "at now任务提交失败" + return + fi + + # 等待触发,最多30秒 + local waited=0 + while [ $waited -lt 30 ]; do + if [ -f "$AT_MARKER" ]; then + tst_res $TPASS "at任务在${waited}秒后触发成功" + return + fi + sleep 2 + waited=$((waited + 2)) + done + tst_res $TFAIL "at任务在30秒内未触发" + ;; + 3) + # 提交一个未来任务然后删除 + local job_output + job_output=$(echo "touch /tmp/atd_should_not_exist_$$" | at now + 10 minutes 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "at任务提交失败" + return + fi + + # 从atq获取任务号 + local job_id + job_id=$(atq 2>/dev/null | tail -1 | awk '{print $1}') + if [ -z "$job_id" ]; then + tst_res $TFAIL "atq未查询到刚提交的任务" + return + fi + + atrm "$job_id" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "atrm删除任务${job_id}失败" + return + fi + + if atq 2>/dev/null | grep -q "^${job_id}[[:space:]]"; then + tst_res $TFAIL "atrm后任务${job_id}仍在队列中" + else + tst_res $TPASS "atrm成功删除任务${job_id}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/chronyd_service_tc003.sh b/os_smoke_src/testcases/baseos/service/chronyd_service_tc003.sh new file mode 100755 index 0000000..6fee006 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/chronyd_service_tc003.sh @@ -0,0 +1,130 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: chronyd_service_tc003 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: chronyd时间同步服务验证 - 验证chrony服务状态、配置及时间源查询能力 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 验证chrony软件包已安装 +# 2. 验证chronyd服务可启动并处于运行状态 +# 3. 验证chrony配置文件/etc/chrony.conf存在且有效 +# 4. 使用chronyc验证时间源查询和时钟跟踪功能 +# 预期结果: +# 1. chrony包已安装或安装成功 +# 2. chronyd服务active(running) +# 3. /etc/chrony.conf存在且包含server/pool配置 +# 4. chronyc sources和chronyc tracking命令执行成功 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "验证chrony软件包安装" + "验证chronyd服务运行状态" + "验证chrony配置文件" + "chronyc时间源查询和跟踪" +) +tcnt=${#TCASE_DESC[@]} + +CHRONYD_WAS_ACTIVE=0 + +setup() +{ + if systemctl is-active --quiet chronyd 2>/dev/null; then + CHRONYD_WAS_ACTIVE=1 + fi +} + +cleanup() +{ + if [ $CHRONYD_WAS_ACTIVE -eq 0 ]; then + systemctl stop chronyd 2>/dev/null + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + install_packages "chrony" + if rpm -q chrony >/dev/null 2>&1; then + tst_res $TPASS "chrony软件包已安装" + else + tst_res $TFAIL "chrony软件包安装失败" + fi + ;; + 1) + if ! systemctl is-active --quiet chronyd 2>/dev/null; then + systemctl start chronyd 2>/dev/null + sleep 2 + fi + if systemctl is-active --quiet chronyd 2>/dev/null; then + tst_res $TPASS "chronyd服务正在运行" + else + if systemctl is-active --quiet ntpd 2>/dev/null; then + tst_res $TCONF "ntpd服务已运行,chronyd无法同时启动" + else + tst_res $TFAIL "chronyd服务无法启动" + fi + fi + ;; + 2) + local conf="/etc/chrony.conf" + if [ ! -f "$conf" ]; then + tst_res $TFAIL "${conf}不存在" + return + fi + + if grep -qE "^[[:space:]]*(server|pool|peer)[[:space:]]" "$conf" 2>/dev/null; then + local source_count + source_count=$(grep -cE "^[[:space:]]*(server|pool|peer)[[:space:]]" "$conf") + tst_res $TPASS "${conf}包含${source_count}条时间源配置" + else + tst_res $TFAIL "${conf}中未找到server/pool/peer配置" + fi + ;; + 3) + ensure_command "chronyc" "chrony" + + if ! systemctl is-active --quiet chronyd 2>/dev/null; then + tst_res $TCONF "chronyd未运行,无法查询时间源" + return + fi + + local fail=0 + + local sources_output + sources_output=$(chronyc sources 2>&1) + if [ $? -eq 0 ]; then + tst_res $TINFO "chronyc sources查询成功" + else + tst_res $TFAIL "chronyc sources执行失败: ${sources_output}" + fail=1 + fi + + local tracking_output + tracking_output=$(chronyc tracking 2>&1) + if [ $? -eq 0 ] && echo "$tracking_output" | grep -qi "Reference ID"; then + tst_res $TINFO "chronyc tracking查询成功" + else + tst_res $TFAIL "chronyc tracking执行失败: ${tracking_output}" + fail=1 + fi + + if [ $fail -eq 0 ]; then + tst_res $TPASS "chronyc时间源查询和跟踪功能正常" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/crond_service_tc002.sh b/os_smoke_src/testcases/baseos/service/crond_service_tc002.sh new file mode 100755 index 0000000..123c9ca --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/crond_service_tc002.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: crond_service_tc002 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: crond定时任务服务验证 - 验证crond服务状态及crontab添加/触发/删除功能 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 验证crond服务处于运行状态 +# 2. 验证crontab命令可用且能列出当前任务 +# 3. 添加一条每分钟执行的crontab任务并验证触发 +# 4. 删除测试crontab任务并确认清理干净 +# 预期结果: +# 1. crond服务active(running) +# 2. crontab -l可正常执行 +# 3. crontab任务在约70秒内触发,标记文件生成 +# 4. 测试任务已从crontab中移除 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "验证crond服务运行状态" + "验证crontab命令可用" + "添加crontab任务并验证触发" + "删除测试crontab任务" +) +tcnt=${#TCASE_DESC[@]} + +CRON_MARKER="/tmp/crond_smoke_test_$$" +CRON_BAK="/tmp/crontab_bak_$$" + +setup() +{ + if ! which crond >/dev/null 2>&1 && ! which cron >/dev/null 2>&1; then + install_packages "cronie" + fi + + crontab -l > "$CRON_BAK" 2>/dev/null || true + rm -f "$CRON_MARKER" + + if ! systemctl is-active --quiet crond 2>/dev/null; then + systemctl start crond 2>/dev/null + fi +} + +cleanup() +{ + if [ -f "$CRON_BAK" ] && [ -s "$CRON_BAK" ]; then + crontab "$CRON_BAK" 2>/dev/null + else + crontab -r 2>/dev/null || true + fi + rm -f "$CRON_BAK" "$CRON_MARKER" +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if systemctl is-active --quiet crond 2>/dev/null; then + tst_res $TPASS "crond服务正在运行" + elif systemctl is-active --quiet cron 2>/dev/null; then + tst_res $TPASS "cron服务正在运行(Debian系)" + else + systemctl start crond 2>/dev/null || systemctl start cron 2>/dev/null + sleep 1 + if systemctl is-active --quiet crond 2>/dev/null || systemctl is-active --quiet cron 2>/dev/null; then + tst_res $TPASS "crond服务启动成功" + else + tst_res $TFAIL "crond服务未运行且无法启动" + fi + fi + ;; + 1) + ensure_command "crontab" "cronie" + crontab -l >/dev/null 2>&1 + local rc=$? + if [ $rc -eq 0 ] || [ $rc -eq 1 ]; then + tst_res $TPASS "crontab命令可用" + else + tst_res $TFAIL "crontab -l执行异常, rc=${rc}" + fi + ;; + 2) + rm -f "$CRON_MARKER" + local new_job="* * * * * touch ${CRON_MARKER}" + + (crontab -l 2>/dev/null; echo "$new_job") | crontab - 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "crontab添加任务失败" + return + fi + + if ! crontab -l 2>/dev/null | grep -qF "$CRON_MARKER"; then + tst_res $TFAIL "crontab中未找到刚添加的任务" + return + fi + tst_res $TINFO "已添加crontab任务,等待触发(最多70秒)..." + + local waited=0 + while [ $waited -lt 70 ]; do + if [ -f "$CRON_MARKER" ]; then + tst_res $TPASS "crontab任务在${waited}秒后触发成功" + return + fi + sleep 5 + waited=$((waited + 5)) + done + tst_res $TFAIL "crontab任务在70秒内未触发" + ;; + 3) + crontab -l 2>/dev/null | grep -vF "$CRON_MARKER" | crontab - 2>/dev/null + if crontab -l 2>/dev/null | grep -qF "$CRON_MARKER"; then + tst_res $TFAIL "测试crontab任务未被删除" + else + tst_res $TPASS "测试crontab任务已清理" + fi + rm -f "$CRON_MARKER" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/dbus_service_tc001.sh b/os_smoke_src/testcases/baseos/service/dbus_service_tc001.sh new file mode 100755 index 0000000..60f5bf2 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/dbus_service_tc001.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: dbus_service_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: D-Bus消息总线服务验证,验证dbus-daemon运行状态及基本通信能力 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 检查dbus软件包已安装 +# 2. 验证dbus-daemon进程运行及系统总线socket可用 +# 3. 使用busctl验证总线服务列表查询能力 +# 4. 使用dbus-send向org.freedesktop.DBus发送方法调用,验证通信功能 +# 预期结果: +# 1. dbus包已安装 +# 2. dbus-daemon进程存在且/var/run/dbus/system_bus_socket可用 +# 3. busctl可列出系统总线上的服务 +# 4. dbus-send方法调用成功返回结果 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "检查dbus软件包安装" + "验证dbus-daemon运行及系统总线socket" + "busctl列出总线服务" + "dbus-send方法调用通信验证" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + : +} + +cleanup() +{ + : +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 检查dbus包安装 + install_packages "dbus" + if rpm -q dbus >/dev/null 2>&1 || rpm -q dbus-daemon >/dev/null 2>&1; then + tst_res $TPASS "dbus软件包已安装" + else + tst_res $TFAIL "dbus软件包安装失败" + fi + ;; + 1) + # 验证dbus-daemon进程运行 + local fail=0 + + if pgrep -x dbus-daemon >/dev/null 2>&1; then + tst_res $TINFO "dbus-daemon进程正在运行" + else + # 尝试通过systemd启动 + systemctl start dbus 2>/dev/null || systemctl start dbus-broker 2>/dev/null + sleep 1 + if pgrep -x dbus-daemon >/dev/null 2>&1 || pgrep -x dbus-broker >/dev/null 2>&1; then + tst_res $TINFO "dbus服务已启动" + else + tst_res $TFAIL "dbus-daemon进程未运行且无法启动" + fail=1 + fi + fi + + # 检查系统总线socket + local bus_socket="" + if [ -S /var/run/dbus/system_bus_socket ]; then + bus_socket="/var/run/dbus/system_bus_socket" + elif [ -S /run/dbus/system_bus_socket ]; then + bus_socket="/run/dbus/system_bus_socket" + fi + + if [ -n "$bus_socket" ]; then + tst_res $TINFO "系统总线socket可用: $bus_socket" + else + tst_res $TFAIL "系统总线socket不存在" + fail=1 + fi + + [ $fail -eq 0 ] && tst_res $TPASS "dbus-daemon运行正常,系统总线socket可用" + ;; + 2) + # busctl列出服务 + ensure_command "busctl" "systemd" + + local service_list + service_list=$(busctl list --no-pager 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "busctl list执行失败: $service_list" + return + fi + + # 检查是否包含核心服务org.freedesktop.DBus + if echo "$service_list" | grep -q "org.freedesktop.DBus"; then + tst_res $TPASS "busctl成功列出总线服务,包含org.freedesktop.DBus" + else + tst_res $TFAIL "busctl未列出核心服务org.freedesktop.DBus" + fi + ;; + 3) + # dbus-send方法调用 + if ! command -v dbus-send >/dev/null 2>&1; then + install_packages "dbus-tools" || install_packages "dbus" + fi + if ! command -v dbus-send >/dev/null 2>&1; then + tst_brk $TBROK "dbus-send命令不可用" + fi + + # 调用org.freedesktop.DBus.ListNames获取所有连接名 + local result + result=$(dbus-send --system --print-reply --dest=org.freedesktop.DBus \ + /org/freedesktop/DBus org.freedesktop.DBus.ListNames 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "dbus-send调用ListNames失败: $result" + return + fi + + # 验证返回结果包含array和org.freedesktop.DBus + if echo "$result" | grep -q "org.freedesktop.DBus"; then + tst_res $TPASS "dbus-send方法调用成功,通信功能正常" + else + tst_res $TFAIL "dbus-send返回结果异常: $result" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/irqbalance_service_tc007.sh b/os_smoke_src/testcases/baseos/service/irqbalance_service_tc007.sh new file mode 100755 index 0000000..f5da710 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/irqbalance_service_tc007.sh @@ -0,0 +1,153 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: irqbalance_service_tc007 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: irqbalance中断均衡服务验证 - 验证服务启停、进程状态及中断分布 +# 前置条件: 系统具备root权限,多核环境 +# 步骤: +# 1. 验证irqbalance软件包已安装 +# 2. 验证irqbalance服务启动和运行状态 +# 3. 验证/proc/interrupts中断分布在多核间存在 +# 4. 验证irqbalance服务可正常停止和重启 +# 预期结果: +# 1. irqbalance包已安装 +# 2. irqbalance服务active(running) +# 3. /proc/interrupts显示中断分布在多个CPU上 +# 4. 服务停止/重启操作正常 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "验证irqbalance软件包安装" + "验证irqbalance服务运行状态" + "检查中断多核分布" + "irqbalance服务停止和重启" +) +tcnt=${#TCASE_DESC[@]} + +IRQBALANCE_WAS_ACTIVE=0 + +setup() +{ + install_packages "irqbalance" + + if systemctl is-active --quiet irqbalance 2>/dev/null; then + IRQBALANCE_WAS_ACTIVE=1 + fi +} + +cleanup() +{ + # 恢复原始状态 + if [ $IRQBALANCE_WAS_ACTIVE -eq 1 ]; then + systemctl start irqbalance 2>/dev/null + else + systemctl stop irqbalance 2>/dev/null + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if rpm -q irqbalance >/dev/null 2>&1; then + local ver + ver=$(rpm -q irqbalance 2>/dev/null) + tst_res $TPASS "irqbalance已安装: ${ver}" + else + tst_res $TFAIL "irqbalance软件包未安装" + fi + ;; + 1) + if ! systemctl is-active --quiet irqbalance 2>/dev/null; then + systemctl start irqbalance 2>/dev/null + sleep 2 + fi + + if systemctl is-active --quiet irqbalance 2>/dev/null; then + tst_res $TPASS "irqbalance服务正在运行" + else + # 单核环境irqbalance可能自动退出 + local cpu_count + cpu_count=$(nproc 2>/dev/null) + if [ "$cpu_count" -le 1 ]; then + tst_res $TCONF "单核环境,irqbalance可能无需运行" + else + tst_res $TFAIL "irqbalance服务无法启动" + fi + fi + ;; + 2) + if [ ! -f /proc/interrupts ]; then + tst_res $TFAIL "/proc/interrupts不存在" + return + fi + + local cpu_count + cpu_count=$(nproc 2>/dev/null) + if [ "$cpu_count" -le 1 ]; then + tst_res $TCONF "单核环境,跳过中断多核分布检查" + return + fi + + # 检查是否有中断分布在多个CPU上 + # 取头部获取CPU列数,然后检查非零列 + local multi_cpu_irqs=0 + while IFS= read -r line; do + # 跳过表头和非数字开头行 + echo "$line" | grep -qE "^[[:space:]]*[0-9]+" || continue + # 统计非零中断计数的CPU数 + local non_zero=0 + for val in $(echo "$line" | awk '{for(i=2;i<=NF;i++){if($i ~ /^[0-9]+$/ && $i > 0) print $i}}'); do + non_zero=$((non_zero + 1)) + done + if [ $non_zero -gt 1 ]; then + multi_cpu_irqs=$((multi_cpu_irqs + 1)) + fi + done < /proc/interrupts + + if [ $multi_cpu_irqs -gt 0 ]; then + tst_res $TPASS "检测到${multi_cpu_irqs}个中断分布在多个CPU上" + else + tst_res $TWARN "未检测到多CPU分布的中断(可能负载过低)" + fi + ;; + 3) + # 停止服务 + systemctl stop irqbalance 2>/dev/null + sleep 1 + if systemctl is-active --quiet irqbalance 2>/dev/null; then + tst_res $TFAIL "irqbalance服务停止失败" + return + fi + tst_res $TINFO "irqbalance服务已停止" + + # 重启服务 + systemctl start irqbalance 2>/dev/null + sleep 2 + if systemctl is-active --quiet irqbalance 2>/dev/null; then + tst_res $TPASS "irqbalance服务停止和重启操作正常" + else + local cpu_count + cpu_count=$(nproc 2>/dev/null) + if [ "$cpu_count" -le 1 ]; then + tst_res $TCONF "单核环境,irqbalance启动后可能自动退出" + else + tst_res $TFAIL "irqbalance服务重启失败" + fi + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/journald_query_tc002.sh b/os_smoke_src/testcases/baseos/service/journald_query_tc002.sh new file mode 100755 index 0000000..7624ac8 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/journald_query_tc002.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: journald_query_tc002 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: systemd-journald服务与journalctl多维度查询验证 - 验证journald服务启停及按内核/unit/优先级/时间/条数查询日志 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 重启systemd-journald服务并检查状态为running +# 2. 使用journalctl -k查询内核日志,验证有输出 +# 3. 使用journalctl -u systemd-journald按unit查询,验证有输出 +# 4. 使用journalctl -p err查询错误级别以上日志,验证命令执行成功 +# 5. 使用journalctl --since按时间范围查询,验证命令执行成功 +# 6. 使用journalctl -n 10查询最近10条日志,验证输出条数正确 +# 预期结果: +# 1. 重启后服务状态为running +# 2. journalctl -k有内核日志输出 +# 3. journalctl -u能按unit过滤到对应日志 +# 4. journalctl -p err命令执行成功 +# 5. journalctl --since命令执行成功 +# 6. journalctl -n 10输出不超过10条 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="systemd-journald服务与journalctl多维度查询验证 - 验证journald服务启停及按内核/unit/优先级/时间/条数查询日志" +tcnt=6 + +setup() +{ + : +} + +cleanup() +{ + : +} + +run_test() +{ + local n=$1 + + case $n in + 0) + systemctl restart systemd-journald 2>/dev/null + sleep 1 + if ! systemctl is-active systemd-journald >/dev/null 2>&1; then + tst_res $TFAIL "systemd-journald重启后未处于运行状态" + return + fi + tst_res $TPASS "systemd-journald重启成功,状态为running" + ;; + 1) + local count + count=$(journalctl -k --no-pager 2>/dev/null | wc -l) + if [ "$count" -le 1 ]; then + tst_res $TFAIL "journalctl -k未查到内核日志(行数=$count)" + return + fi + tst_res $TPASS "journalctl -k查询内核日志成功,共${count}行" + ;; + 2) + if ! journalctl -u systemd-journald --no-pager 2>/dev/null | grep -qi "journal"; then + tst_res $TFAIL "journalctl -u systemd-journald未过滤到相关日志" + return + fi + tst_res $TPASS "journalctl -u按unit过滤日志成功" + ;; + 3) + journalctl -p err --no-pager -n 50 >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "journalctl -p err执行失败" + return + fi + tst_res $TPASS "journalctl -p err按优先级查询成功" + ;; + 4) + local since_time + since_time=$(date -d "1 hour ago" "+%Y-%m-%d %H:%M:%S" 2>/dev/null) + if [ -z "$since_time" ]; then + since_time="today" + fi + journalctl --since "$since_time" --no-pager -n 50 >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "journalctl --since '$since_time'执行失败" + return + fi + tst_res $TPASS "journalctl --since按时间查询成功" + ;; + 5) + local count + count=$(journalctl -n 10 --no-pager 2>/dev/null | grep -v "^--" | wc -l) + if [ "$count" -lt 1 ]; then + tst_res $TFAIL "journalctl -n 10无输出" + return + fi + # 标题行+最多10条数据,允许标题行存在 + if [ "$count" -gt 11 ]; then + tst_res $TFAIL "journalctl -n 10输出${count}行,超出预期" + return + fi + tst_res $TPASS "journalctl -n 10查询最近日志成功,共${count}行" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/kernel_log_record_tc004.sh b/os_smoke_src/testcases/baseos/service/kernel_log_record_tc004.sh new file mode 100755 index 0000000..665ba37 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/kernel_log_record_tc004.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: kernel_log_record_tc004 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 内核异常日志记录与回溯验证 - 验证sysrq触发OOM后dmesg和/var/log/messages均能记录内核日志 +# 前置条件: 系统具备root权限,/proc/sysrq-trigger可用 +# 步骤: +# 1. 确保rsyslog运行,清理dmesg缓冲区 +# 2. 通过sysrq触发OOM killer信息 +# 3. 检查dmesg中是否记录了OOM内核日志 +# 4. 检查/var/log/messages中是否记录了OOM日志 +# 预期结果: +# 1. rsyslog运行正常,dmesg清理成功 +# 2. sysrq触发OOM成功 +# 3. dmesg中包含oom关键字 +# 4. /var/log/messages中包含oom关键字 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="内核异常日志记录与回溯验证 - 验证sysrq触发OOM后dmesg和/var/log/messages均能记录内核日志" +tcnt=4 + +RSYSLOG_STARTED=0 + +setup() +{ + if ! systemctl is-active rsyslog >/dev/null 2>&1; then + install_packages "rsyslog" + systemctl start rsyslog 2>/dev/null + sleep 2 + RSYSLOG_STARTED=1 + fi +} + +cleanup() +{ + # 清除本次触发的OOM内核日志,避免框架check_dmesg误检 + dmesg -c > /dev/null 2>&1 + if [ "$RSYSLOG_STARTED" -eq 1 ]; then + systemctl stop rsyslog 2>/dev/null + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if ! systemctl is-active rsyslog >/dev/null 2>&1; then + tst_res $TFAIL "rsyslog未处于运行状态" + return + fi + dmesg -c > /dev/null 2>&1 + tst_res $TPASS "rsyslog运行正常,dmesg缓冲区已清理" + ;; + 1) + if [ ! -f /proc/sysrq-trigger ]; then + tst_brk $TCONF "/proc/sysrq-trigger不存在,跳过测试" + fi + echo f > /proc/sysrq-trigger + sleep 8 + tst_res $TPASS "sysrq触发OOM完成" + ;; + 2) + if dmesg | grep -qi "oom"; then + tst_res $TPASS "dmesg中包含OOM内核日志记录" + else + tst_res $TFAIL "dmesg中未找到OOM内核日志记录" + fi + ;; + 3) + if [ ! -f /var/log/messages ]; then + tst_res $TFAIL "/var/log/messages文件不存在" + return + fi + if tail -n 2000 /var/log/messages | grep -qi "oom"; then + tst_res $TPASS "/var/log/messages中包含OOM日志记录" + else + tst_res $TFAIL "/var/log/messages中未找到OOM日志记录" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/log_access_control_tc005.sh b/os_smoke_src/testcases/baseos/service/log_access_control_tc005.sh new file mode 100755 index 0000000..91b739d --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/log_access_control_tc005.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: log_access_control_tc005 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 日志文件权限与访问控制验证 - 验证关键日志文件权限为600且普通用户无法读取,授权后可读取 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 创建测试普通用户 +# 2. 检查关键日志文件权限为600(/var/log/messages, /var/log/secure, /var/log/cron) +# 3. 普通用户尝试读取日志文件,验证被拒绝 +# 4. 修改/var/log/messages权限为644后普通用户可读取,然后恢复权限 +# 预期结果: +# 1. 测试用户创建成功 +# 2. 日志文件权限均为600 +# 3. 普通用户无法读取日志文件 +# 4. 放开权限后可读取,恢复后不可读取 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="日志文件权限与访问控制验证 - 验证关键日志文件权限为600且普通用户无法读取,授权后可读取" +tcnt=4 + +TEST_USER="smoke_log_test_user" + +setup() +{ + # 清理可能残留的用户 + userdel -r "$TEST_USER" 2>/dev/null + useradd "$TEST_USER" 2>/dev/null + if ! id "$TEST_USER" >/dev/null 2>&1; then + tst_brk $TCONF "创建测试用户${TEST_USER}失败" + fi +} + +cleanup() +{ + # 确保权限恢复 + chmod 600 /var/log/messages 2>/dev/null + userdel -r "$TEST_USER" 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if id "$TEST_USER" >/dev/null 2>&1; then + tst_res $TPASS "测试用户${TEST_USER}创建成功" + else + tst_res $TFAIL "测试用户${TEST_USER}不存在" + fi + ;; + 1) + local fail=0 + for f in /var/log/messages /var/log/secure /var/log/cron; do + if [ ! -f "$f" ]; then + tst_res $TINFO "日志文件$f不存在,跳过权限检查" + continue + fi + local perm + perm=$(stat -c "%a" "$f" 2>/dev/null) + if [ "$perm" != "600" ]; then + tst_res $TFAIL "$f 权限为$perm,期望600" + fail=1 + fi + done + if [ $fail -eq 0 ]; then + tst_res $TPASS "关键日志文件权限均为600" + fi + ;; + 2) + local fail=0 + for f in /var/log/messages /var/log/secure /var/log/cron; do + if [ ! -f "$f" ]; then + continue + fi + if su - "$TEST_USER" -c "head -1 $f" >/dev/null 2>&1; then + tst_res $TFAIL "普通用户不应能读取$f" + fail=1 + fi + done + if [ $fail -eq 0 ]; then + tst_res $TPASS "普通用户无法读取受保护的日志文件" + fi + ;; + 3) + chmod 644 /var/log/messages 2>/dev/null + if ! su - "$TEST_USER" -c "head -1 /var/log/messages" >/dev/null 2>&1; then + tst_res $TFAIL "放开权限后普通用户仍无法读取/var/log/messages" + chmod 600 /var/log/messages 2>/dev/null + return + fi + # 恢复权限 + chmod 600 /var/log/messages 2>/dev/null + if su - "$TEST_USER" -c "head -1 /var/log/messages" >/dev/null 2>&1; then + tst_res $TFAIL "恢复权限后普通用户仍能读取/var/log/messages" + return + fi + tst_res $TPASS "权限控制正确:644时可读,恢复600后不可读" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/logrotate_tc003.sh b/os_smoke_src/testcases/baseos/service/logrotate_tc003.sh new file mode 100755 index 0000000..1f928cc --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/logrotate_tc003.sh @@ -0,0 +1,156 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: logrotate_tc003 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: logrotate日志轮转验证 - 验证日志轮转配置创建、多次轮转及rotate数量淘汰机制 +# 前置条件: 系统具备root权限,已安装logrotate +# 步骤: +# 1. 创建测试用logrotate配置(rotate 3),生成日志并触发第1次轮转 +# 2. 第2次轮转,验证.1为新内容、.2为旧.1内容 +# 3. 第3次轮转,验证.1 .2 .3内容正确 +# 4. 第4次轮转,验证.1 .2 .3内容正确且.4不存在(rotate 3淘汰) +# 预期结果: +# 1. 轮转成功,.1存在且内容正确,.2 .3 .4不存在 +# 2. .1 .2存在且内容正确,.3 .4不存在 +# 3. .1 .2 .3存在且内容正确,.4不存在 +# 4. .1 .2 .3存在且内容正确,.4不存在,最早的日志被淘汰 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="logrotate日志轮转验证 - 验证日志轮转配置创建、多次轮转及rotate数量淘汰机制" +tcnt=4 + +LOG_FILE="/var/log/smoke_logrotate_test.log" +CONF_FILE="/etc/logrotate.d/smoke_logrotate_test" + +setup() +{ + ensure_command "logrotate" "logrotate" + # 创建logrotate配置 + cat > "$CONF_FILE" </dev/null +} + +cleanup() +{ + rm -f "$CONF_FILE" 2>/dev/null + rm -f "${LOG_FILE}"* 2>/dev/null +} + +_gen_log() +{ + local mark="$1" + local i + for i in $(seq 100); do + printf "%s_%04d\n" "$mark" "$i" + done > "$LOG_FILE" +} + +run_test() +{ + local n=$1 + + case $n in + 0) + _gen_log "round1" + /usr/sbin/logrotate -f "$CONF_FILE" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "第1次logrotate执行失败" + return + fi + if [ ! -f "${LOG_FILE}.1" ]; then + tst_res $TFAIL "第1次轮转后${LOG_FILE}.1不存在" + return + fi + if ! grep -q "round1_0001" "${LOG_FILE}.1"; then + tst_res $TFAIL "第1次轮转后${LOG_FILE}.1内容不正确" + return + fi + local fail=0 + [ -f "${LOG_FILE}.2" ] && fail=1 + [ -f "${LOG_FILE}.3" ] && fail=1 + [ -f "${LOG_FILE}.4" ] && fail=1 + if [ $fail -ne 0 ]; then + tst_res $TFAIL "第1次轮转后不应存在.2 .3 .4文件" + return + fi + tst_res $TPASS "第1次轮转成功,.1内容正确" + ;; + 1) + _gen_log "round2" + /usr/sbin/logrotate -f "$CONF_FILE" 2>/dev/null + if ! grep -q "round2_0001" "${LOG_FILE}.1" 2>/dev/null; then + tst_res $TFAIL "第2次轮转后.1内容不正确" + return + fi + if ! grep -q "round1_0001" "${LOG_FILE}.2" 2>/dev/null; then + tst_res $TFAIL "第2次轮转后.2内容不正确(应为round1)" + return + fi + if [ -f "${LOG_FILE}.3" ] || [ -f "${LOG_FILE}.4" ]; then + tst_res $TFAIL "第2次轮转后不应存在.3 .4文件" + return + fi + tst_res $TPASS "第2次轮转成功,.1 .2内容正确" + ;; + 2) + _gen_log "round3" + /usr/sbin/logrotate -f "$CONF_FILE" 2>/dev/null + if ! grep -q "round3_0001" "${LOG_FILE}.1" 2>/dev/null; then + tst_res $TFAIL "第3次轮转后.1内容不正确" + return + fi + if ! grep -q "round2_0001" "${LOG_FILE}.2" 2>/dev/null; then + tst_res $TFAIL "第3次轮转后.2内容不正确" + return + fi + if ! grep -q "round1_0001" "${LOG_FILE}.3" 2>/dev/null; then + tst_res $TFAIL "第3次轮转后.3内容不正确" + return + fi + if [ -f "${LOG_FILE}.4" ]; then + tst_res $TFAIL "第3次轮转后不应存在.4文件" + return + fi + tst_res $TPASS "第3次轮转成功,.1 .2 .3内容正确" + ;; + 3) + _gen_log "round4" + /usr/sbin/logrotate -f "$CONF_FILE" 2>/dev/null + if ! grep -q "round4_0001" "${LOG_FILE}.1" 2>/dev/null; then + tst_res $TFAIL "第4次轮转后.1内容不正确" + return + fi + if ! grep -q "round3_0001" "${LOG_FILE}.2" 2>/dev/null; then + tst_res $TFAIL "第4次轮转后.2内容不正确" + return + fi + if ! grep -q "round2_0001" "${LOG_FILE}.3" 2>/dev/null; then + tst_res $TFAIL "第4次轮转后.3内容不正确" + return + fi + if [ -f "${LOG_FILE}.4" ]; then + tst_res $TFAIL "第4次轮转后.4仍存在,rotate 3淘汰机制失效" + return + fi + tst_res $TPASS "第4次轮转成功,rotate 3淘汰机制正常,最早日志已删除" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/polkit_service_tc004.sh b/os_smoke_src/testcases/baseos/service/polkit_service_tc004.sh new file mode 100755 index 0000000..75ac458 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/polkit_service_tc004.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: polkit_service_tc004 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: polkit权限授权框架验证 - 验证polkitd服务状态、pkaction/pkcheck命令及规则加载 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 验证polkit软件包已安装 +# 2. 验证polkitd进程运行状态 +# 3. 验证pkaction命令可列出已注册的授权动作 +# 4. 验证polkit规则目录和配置文件存在 +# 预期结果: +# 1. polkit包已安装 +# 2. polkitd进程正在运行 +# 3. pkaction可列出授权动作且包含org.freedesktop相关条目 +# 4. /usr/share/polkit-1/actions/目录存在且包含.policy文件 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "验证polkit软件包安装" + "验证polkitd进程运行状态" + "pkaction列出授权动作" + "polkit规则目录和配置文件检查" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + install_packages "polkit" +} + +cleanup() +{ + : +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if rpm -q polkit >/dev/null 2>&1; then + local ver + ver=$(rpm -q polkit 2>/dev/null) + tst_res $TPASS "polkit已安装: ${ver}" + else + tst_res $TFAIL "polkit软件包未安装" + fi + ;; + 1) + # polkitd 可能是 polkitd 或 polkit-agent 进程 + if pgrep -x polkitd >/dev/null 2>&1; then + tst_res $TPASS "polkitd进程正在运行" + elif systemctl is-active --quiet polkit 2>/dev/null; then + tst_res $TPASS "polkit服务处于active状态" + else + # 尝试启动 + systemctl start polkit 2>/dev/null + sleep 1 + if systemctl is-active --quiet polkit 2>/dev/null || pgrep -x polkitd >/dev/null 2>&1; then + tst_res $TPASS "polkit服务启动成功" + else + tst_res $TFAIL "polkitd进程未运行且无法启动" + fi + fi + ;; + 2) + ensure_command "pkaction" "polkit" + + local action_list + action_list=$(pkaction 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "pkaction执行失败: ${action_list}" + return + fi + + local action_count + action_count=$(echo "$action_list" | wc -l) + if [ "$action_count" -gt 0 ] && echo "$action_list" | grep -q "org.freedesktop"; then + tst_res $TPASS "pkaction列出${action_count}个授权动作,包含org.freedesktop条目" + else + tst_res $TFAIL "pkaction未返回有效的授权动作列表" + fi + ;; + 3) + local actions_dir="/usr/share/polkit-1/actions" + if [ ! -d "$actions_dir" ]; then + tst_res $TFAIL "${actions_dir}目录不存在" + return + fi + + local policy_count + policy_count=$(find "$actions_dir" -name "*.policy" 2>/dev/null | wc -l) + if [ "$policy_count" -gt 0 ]; then + tst_res $TPASS "${actions_dir}包含${policy_count}个.policy文件" + else + tst_res $TFAIL "${actions_dir}下无.policy文件" + fi + + # 检查规则目录 + local rules_dir="/usr/share/polkit-1/rules.d" + if [ -d "$rules_dir" ]; then + tst_res $TINFO "规则目录${rules_dir}存在" + else + tst_res $TINFO "规则目录${rules_dir}不存在(可能使用/etc/polkit-1/)" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/postfix_service_tc008.sh b/os_smoke_src/testcases/baseos/service/postfix_service_tc008.sh new file mode 100755 index 0000000..fb96fbf --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/postfix_service_tc008.sh @@ -0,0 +1,168 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: postfix_service_tc008 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: postfix邮件服务验证 - 验证postfix安装、配置检查、服务启停及邮件队列功能 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 验证postfix软件包安装及配置文件完整性 +# 2. 验证postfix配置文件语法正确 +# 3. 启动postfix服务并验证运行状态和端口监听 +# 4. 验证邮件队列及版本查询功能 +# 预期结果: +# 1. postfix已安装,main.cf和master.cf存在 +# 2. postfix check配置语法检查通过 +# 3. postfix服务active,监听25端口 +# 4. postqueue/mailq/postconf命令正常工作 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "验证postfix安装及配置文件" + "postfix配置语法检查" + "postfix服务启动及端口监听" + "邮件队列及版本查询" +) +tcnt=${#TCASE_DESC[@]} + +PKG_INSTALLED=0 +POSTFIX_WAS_ACTIVE=0 +CONFLICT_PKG_REMOVED="" + +setup() +{ + if systemctl is-active --quiet postfix 2>/dev/null; then + POSTFIX_WAS_ACTIVE=1 + fi + + if ! rpm -q postfix >/dev/null 2>&1; then + # 卸载冲突的MTA包 + for pkg in tlinux-sendmail sendmail; do + if rpm -q "$pkg" >/dev/null 2>&1; then + tst_res $TINFO "卸载冲突包: $pkg" + yum remove -y "$pkg" >/dev/null 2>&1 + CONFLICT_PKG_REMOVED="$pkg" + fi + done + install_packages "postfix" + if ! rpm -q postfix >/dev/null 2>&1; then + tst_brk $TCONF "postfix安装失败,可能存在包冲突" + fi + PKG_INSTALLED=1 + fi + + systemctl stop postfix 2>/dev/null || true + sleep 1 +} + +cleanup() +{ + systemctl stop postfix 2>/dev/null || true + + if [ $POSTFIX_WAS_ACTIVE -eq 1 ]; then + systemctl start postfix 2>/dev/null + fi + + if [ $PKG_INSTALLED -eq 1 ]; then + remove_packages "postfix" + # 恢复之前卸载的冲突包 + if [ -n "$CONFLICT_PKG_REMOVED" ]; then + install_packages "$CONFLICT_PKG_REMOVED" + fi + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if ! rpm -q postfix >/dev/null 2>&1; then + tst_res $TFAIL "postfix软件包未安装" + return + fi + + local fail=0 + if [ ! -f /etc/postfix/main.cf ]; then + tst_res $TFAIL "postfix main.cf配置文件不存在" + fail=1 + fi + if [ ! -f /etc/postfix/master.cf ]; then + tst_res $TFAIL "postfix master.cf配置文件不存在" + fail=1 + fi + [ $fail -eq 0 ] && tst_res $TPASS "postfix已安装,配置文件完整" + ;; + 1) + ensure_command "postfix" + + local output + output=$(postfix check 2>&1) + if [ $? -eq 0 ]; then + tst_res $TPASS "postfix配置语法检查通过" + else + tst_res $TFAIL "postfix配置语法错误: ${output}" + fi + ;; + 2) + systemctl start postfix 2>/dev/null + sleep 2 + + local fail=0 + if ! systemctl is-active --quiet postfix 2>/dev/null; then + tst_res $TFAIL "postfix服务未运行" + fail=1 + else + tst_res $TINFO "postfix服务已启动" + fi + + if ss -tlnp 2>/dev/null | grep -q ":25 "; then + tst_res $TINFO "postfix正在监听25端口" + else + tst_res $TFAIL "postfix未监听25端口" + fail=1 + fi + + [ $fail -eq 0 ] && tst_res $TPASS "postfix服务启动正常,监听25端口" + ;; + 3) + local fail=0 + + if ! postqueue -p >/dev/null 2>&1; then + tst_res $TFAIL "postqueue -p执行失败" + fail=1 + else + tst_res $TINFO "postqueue命令正常" + fi + + if ! mailq >/dev/null 2>&1; then + tst_res $TFAIL "mailq命令执行失败" + fail=1 + else + tst_res $TINFO "mailq命令正常" + fi + + local version + version=$(postconf mail_version 2>&1) + if [ $? -eq 0 ]; then + tst_res $TINFO "postfix版本: ${version}" + else + tst_res $TFAIL "postconf mail_version查询失败" + fail=1 + fi + + [ $fail -eq 0 ] && tst_res $TPASS "邮件队列及版本查询功能正常" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/rsyslog_service_tc001.sh b/os_smoke_src/testcases/baseos/service/rsyslog_service_tc001.sh new file mode 100755 index 0000000..ab9b537 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/rsyslog_service_tc001.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: rsyslog_service_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: rsyslog服务管理与日志写入验证 - 验证rsyslog安装启停、logger写入/var/log/messages及关键日志文件存在性 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 安装rsyslog并启动服务,检查服务状态为running +# 2. 使用logger写入测试消息到syslog,检查/var/log/messages中包含该消息 +# 3. 重启rsyslog服务,检查服务状态恢复为running +# 4. 检查关键日志文件存在性(/var/log/messages, /var/log/secure, /var/log/cron) +# 5. 检查rsyslog配置中包含/var/log/messages写入规则 +# 预期结果: +# 1. rsyslog启动成功,服务状态为running +# 2. /var/log/messages中包含logger写入的测试消息 +# 3. 重启后服务状态为running +# 4. 关键日志文件均存在 +# 5. rsyslog配置中包含/var/log/messages规则 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="rsyslog服务管理与日志写入验证 - 验证rsyslog安装启停、logger写入/var/log/messages及关键日志文件存在性" +tcnt=5 + +setup() +{ + install_packages "rsyslog" + systemctl restart rsyslog 2>/dev/null + sleep 3 +} + +cleanup() +{ + : +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if ! systemctl is-active rsyslog >/dev/null 2>&1; then + tst_res $TFAIL "rsyslog服务未处于运行状态" + return + fi + tst_res $TPASS "rsyslog服务启动成功,状态为running" + ;; + 1) + local tag="smoke_test_$$_$(date +%s)" + logger -t "$tag" "rsyslog_service_tc001 test message" + local found=0 + for i in $(seq 1 10); do + sleep 3 + if grep -q "$tag" /var/log/messages 2>/dev/null; then + found=1 + break + fi + done + if [ $found -eq 0 ]; then + tst_res $TFAIL "logger写入后/var/log/messages中未找到标签$tag" + return + fi + tst_res $TPASS "logger写入测试消息成功,/var/log/messages中已记录" + ;; + 2) + systemctl restart rsyslog 2>/dev/null + sleep 2 + if ! systemctl is-active rsyslog >/dev/null 2>&1; then + tst_res $TFAIL "rsyslog重启后未处于运行状态" + return + fi + tst_res $TPASS "rsyslog重启成功,状态为running" + ;; + 3) + local fail=0 + for f in /var/log/messages /var/log/secure /var/log/cron; do + if [ ! -f "$f" ]; then + tst_res $TFAIL "关键日志文件 $f 不存在" + fail=1 + fi + done + if [ $fail -eq 0 ]; then + tst_res $TPASS "关键日志文件(/var/log/messages, secure, cron)均存在" + fi + ;; + 4) + if grep -rn "/var/log/messages" /etc/rsyslog.conf /etc/rsyslog.d/ >/dev/null 2>&1; then + tst_res $TPASS "rsyslog配置中包含/var/log/messages写入规则" + else + tst_res $TFAIL "rsyslog配置中未找到/var/log/messages写入规则" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/snmp_tool_tc001.sh b/os_smoke_src/testcases/baseos/service/snmp_tool_tc001.sh new file mode 100755 index 0000000..dd3e1d3 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/snmp_tool_tc001.sh @@ -0,0 +1,163 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: snmp_tool_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: SNMP协议工具包功能测试,支持SNMP设备和操作信息检索 +# 前置条件: 系统支持yum包管理器,具备root权限 +# 步骤: +# 1. 安装SNMP相关工具包并配置服务 +# 2. 启动SNMP服务并验证监听端口 +# 3. 使用snmpwalk查询系统信息 +# 4. 使用snmpget检索设备名称、描述和ObjectID +# 预期结果: +# 1. SNMP软件包安装成功,配置文件生成正确 +# 2. snmpd服务启动成功,监听UDP 161端口 +# 3. snmpwalk成功获取系统信息 +# 4. snmpget成功检索sysName/sysDescr/sysObjectID +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "安装SNMP工具包并配置服务" + "启动SNMP服务并验证监听端口" + "snmpwalk查询系统信息" + "snmpget检索设备信息" +) +tcnt=${#TCASE_DESC[@]} + +SNMP_WAS_INSTALLED=0 +SNMP_UTILS_WAS_INSTALLED=0 +SNMPD_WAS_ACTIVE=0 +SNMPD_CONF_BACKED=0 + +setup() +{ + # 记录snmp原始安装/服务状态 + if rpm -q net-snmp >/dev/null 2>&1; then + SNMP_WAS_INSTALLED=1 + fi + if rpm -q net-snmp-utils >/dev/null 2>&1; then + SNMP_UTILS_WAS_INSTALLED=1 + fi + if systemctl is-active --quiet snmpd 2>/dev/null; then + SNMPD_WAS_ACTIVE=1 + fi + + # 停止已有服务以避免配置冲突 + systemctl stop snmpd 2>/dev/null + pkill snmpd 2>/dev/null + + # 备份原始配置 + if [ -f /etc/snmp/snmpd.conf ]; then + cp /etc/snmp/snmpd.conf /etc/snmp/snmpd.conf.bak.$$ 2>/dev/null + SNMPD_CONF_BACKED=1 + fi +} + +cleanup() +{ + systemctl stop snmpd 2>/dev/null + pkill snmpd 2>/dev/null + sleep 2 + + # 恢复原始配置 + if [ $SNMPD_CONF_BACKED -eq 1 ] && [ -f "/etc/snmp/snmpd.conf.bak.$$" ]; then + cp "/etc/snmp/snmpd.conf.bak.$$" /etc/snmp/snmpd.conf 2>/dev/null + rm -f "/etc/snmp/snmpd.conf.bak.$$" + fi + + # 恢复原始安装/服务状态 + if [ $SNMP_WAS_INSTALLED -eq 0 ]; then + remove_packages "net-snmp" + fi + if [ $SNMP_UTILS_WAS_INSTALLED -eq 0 ]; then + remove_packages "net-snmp-utils" + fi + + if [ $SNMPD_WAS_ACTIVE -eq 1 ]; then + systemctl start snmpd 2>/dev/null + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + install_packages "net-snmp net-snmp-utils" + if ! which snmpwalk >/dev/null 2>&1 || ! which snmpget >/dev/null 2>&1; then + tst_brk $TCONF "net-snmp-utils 安装失败" + fi + + # 配置SNMP服务 + cat > /etc/snmp/snmpd.conf << 'EOF' +rocommunity public localhost +rocommunity public 127.0.0.1 +syslocation "Test Environment" +syscontact "Test Admin " +view systemview included .1.3.6.1.2.1.1 +view systemview included .1.3.6.1.2.1.25.1.1 +dontLogTCPWrappersConnects yes +agentAddress udp:161 +EOF + if [ $? -ne 0 ]; then + tst_res $TFAIL "SNMP配置文件创建失败" + return + fi + tst_res $TPASS "SNMP工具包安装和配置成功" + ;; + 1) + systemctl start snmpd.service 2>/dev/null + sleep 3 + if ! systemctl is-active --quiet snmpd; then + tst_res $TFAIL "snmpd服务未正常运行" + return + fi + + ss -ulnp | grep -q :161 + if [ $? -ne 0 ]; then + tst_res $TFAIL "SNMP服务未监听161端口" + return + fi + tst_res $TPASS "SNMP服务启动成功,监听161端口" + ;; + 2) + timeout 10 snmpwalk -v 2c -c public localhost system >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "snmpwalk查询系统信息失败" + return + fi + tst_res $TPASS "snmpwalk查询系统信息成功" + ;; + 3) + local fail=0 + timeout 10 snmpget -v2c -c public localhost sysName.0 >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "snmpget检索sysName失败" + fail=1 + fi + timeout 10 snmpget -v2c -c public localhost sysDescr.0 >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "snmpget检索sysDescr失败" + fail=1 + fi + timeout 10 snmpget -v2c -c public localhost sysObjectID.0 >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "snmpget检索sysObjectID失败" + fail=1 + fi + [ $fail -eq 0 ] && tst_res $TPASS "snmpget检索设备信息全部成功" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/sshd_tc001.sh b/os_smoke_src/testcases/baseos/service/sshd_tc001.sh new file mode 100755 index 0000000..ac9dd66 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/sshd_tc001.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: sshd_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: SSH服务验证 - 验证sshd服务状态、端口监听及本地SSH连接能力 +# 前置条件: 系统已安装openssh-server,具备root权限 +# 步骤: +# 1. 检查sshd服务处于active(running)状态 +# 2. 验证sshd监听22端口(或自定义端口) +# 3. 验证sshd配置文件语法正确 +# 4. 通过ssh localhost执行命令验证实际连接能力 +# 预期结果: +# 1. systemctl status sshd显示active(running) +# 2. ss -tlnp显示sshd监听对应端口 +# 3. sshd -t配置检查返回0 +# 4. ssh localhost执行命令成功返回预期结果 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "检查sshd服务处于运行状态" + "验证sshd监听端口" + "验证sshd配置文件语法" + "ssh本地连接执行命令" +) +tcnt=${#TCASE_DESC[@]} + +SSHD_PORT="" + +setup() +{ + # 确保sshd已安装并运行 + install_packages "openssh-server" + + # 获取sshd实际监听端口 + SSHD_PORT=$(ss -tlnp 2>/dev/null | grep sshd | awk '{print $4}' | grep -oE '[0-9]+$' | head -1) + if [ -z "$SSHD_PORT" ]; then + SSHD_PORT=22 + fi +} + +cleanup() +{ + : +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 检查sshd服务运行状态 + if ! systemctl is-active --quiet sshd; then + systemctl start sshd 2>/dev/null + sleep 1 + if ! systemctl is-active --quiet sshd; then + tst_res $TFAIL "sshd服务未运行且无法启动" + return + fi + fi + local status + status=$(systemctl is-active sshd 2>/dev/null) + tst_res $TPASS "sshd服务状态: ${status}" + ;; + 1) + # 验证sshd监听端口 + local listen_info + listen_info=$(ss -tlnp 2>/dev/null | grep sshd) + if [ -z "$listen_info" ]; then + tst_res $TFAIL "sshd未监听任何端口" + return + fi + if echo "$listen_info" | grep -qE ":${SSHD_PORT}\b"; then + tst_res $TPASS "sshd正在监听端口${SSHD_PORT}" + else + tst_res $TFAIL "sshd未监听预期端口${SSHD_PORT}, 实际: ${listen_info}" + fi + ;; + 2) + # 验证sshd配置文件语法 + ensure_command "sshd" "openssh-server" + local output + output=$(sshd -t 2>&1) + if [ $? -eq 0 ]; then + tst_res $TPASS "sshd配置文件语法检查通过" + else + tst_res $TFAIL "sshd配置文件语法错误: ${output}" + fi + ;; + 3) + # ssh本地连接执行命令 + local test_token="sshd_smoke_test_$$" + local result + result=$(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 \ + -o BatchMode=yes -o PasswordAuthentication=no \ + -p "$SSHD_PORT" localhost "echo ${test_token}" 2>/dev/null) + if [ "$result" = "$test_token" ]; then + tst_res $TPASS "ssh localhost连接并执行命令成功" + return + fi + + # BatchMode失败,可能无免密key,降级检测端口连通性 + local banner + banner=$(timeout 5 bash -c "cat < /dev/tcp/localhost/${SSHD_PORT}" 2>/dev/null | head -1) + if echo "$banner" | grep -qi "ssh"; then + tst_res $TCONF "sshd端口${SSHD_PORT}响应SSH banner正常(${banner}),但无免密key无法完整测试" + else + tst_res $TFAIL "ssh localhost连接失败,端口${SSHD_PORT}无SSH响应" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/sysctl_tool_tc001.sh b/os_smoke_src/testcases/baseos/service/sysctl_tool_tc001.sh new file mode 100755 index 0000000..4b88781 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/sysctl_tool_tc001.sh @@ -0,0 +1,128 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: sysctl_tool_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: sysctl配置管理工具功能验证,验证查看/修改/还原内核参数能力 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 安装procps,检查sysctl工具可用 +# 2. 查看kernel.printk配置,与/proc/sys/kernel/printk对比一致 +# 3. 修改kernel.printk为"5 4 1 7",验证修改生效 +# 4. 还原kernel.printk为原始值,验证恢复成功 +# 预期结果: +# 1. sysctl工具安装且可用 +# 2. sysctl读取值与/proc/sys/kernel/printk一致 +# 3. 修改后两处读取均为"5 4 1 7" +# 4. 还原后两处读取均为原始值 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "检查sysctl工具可用" + "查看kernel.printk与proc一致性" + "修改kernel.printk并验证" + "还原kernel.printk并验证" +) +tcnt=${#TCASE_DESC[@]} + +ORI_PRINTK="" + +setup() +{ + # 保存printk原始值,确保任何情况都能恢复 + ORI_PRINTK=$(cat /proc/sys/kernel/printk 2>/dev/null) + if [ -z "$ORI_PRINTK" ]; then + tst_brk $TFAIL "无法读取/proc/sys/kernel/printk" + fi +} + +cleanup() +{ + # 无条件恢复printk原始值 + if [ -n "$ORI_PRINTK" ]; then + sysctl -w kernel.printk="$ORI_PRINTK" >/dev/null 2>&1 + tst_res $TINFO "已恢复kernel.printk为: $ORI_PRINTK" + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 检查sysctl工具 + ensure_command "sysctl" "procps-ng" + tst_res $TPASS "sysctl工具可用" + ;; + 1) + # 对比sysctl与/proc读取值 + local proc_val sysctl_val + proc_val=$(cat /proc/sys/kernel/printk 2>/dev/null) + sysctl_val=$(sysctl kernel.printk 2>/dev/null | awk -F "kernel.printk = " '{print $2}') + + if [ "$proc_val" = "$sysctl_val" ]; then + tst_res $TPASS "sysctl读取与/proc/sys/kernel/printk一致: $proc_val" + else + tst_res $TFAIL "不一致: proc='$proc_val' sysctl='$sysctl_val'" + fi + ;; + 2) + # 修改kernel.printk + local test_val="5 4 1 7" + sysctl -w kernel.printk="5 4 1 7" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "sysctl -w kernel.printk='5 4 1 7' 执行失败" + return + fi + + local proc_val sysctl_val + proc_val=$(cat /proc/sys/kernel/printk 2>/dev/null) + sysctl_val=$(sysctl kernel.printk 2>/dev/null | awk -F "kernel.printk = " '{print $2}') + + local fail=0 + if ! echo "$proc_val" | grep -qE "^5\s+4\s+1\s+7$"; then + tst_res $TFAIL "/proc/sys/kernel/printk修改未生效: $proc_val" + fail=1 + fi + if ! echo "$sysctl_val" | grep -qE "^5\s+4\s+1\s+7$"; then + tst_res $TFAIL "sysctl读取修改未生效: $sysctl_val" + fail=1 + fi + [ $fail -eq 0 ] && tst_res $TPASS "kernel.printk修改为5 4 1 7成功" + ;; + 3) + # 还原kernel.printk + sysctl -w kernel.printk="$ORI_PRINTK" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "sysctl -w kernel.printk='$ORI_PRINTK' 还原失败" + return + fi + + local proc_val sysctl_val + proc_val=$(cat /proc/sys/kernel/printk 2>/dev/null) + sysctl_val=$(sysctl kernel.printk 2>/dev/null | awk -F "kernel.printk = " '{print $2}') + + local fail=0 + if [ "$proc_val" != "$ORI_PRINTK" ]; then + tst_res $TFAIL "/proc还原后不一致: expect='$ORI_PRINTK' got='$proc_val'" + fail=1 + fi + if [ "$sysctl_val" != "$ORI_PRINTK" ]; then + tst_res $TFAIL "sysctl还原后不一致: expect='$ORI_PRINTK' got='$sysctl_val'" + fail=1 + fi + [ $fail -eq 0 ] && tst_res $TPASS "kernel.printk还原为原始值成功" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/systemd_basic_func_tc001.sh b/os_smoke_src/testcases/baseos/service/systemd_basic_func_tc001.sh new file mode 100755 index 0000000..5cbb2bf --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/systemd_basic_func_tc001.sh @@ -0,0 +1,130 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: systemd_basic_func_tc001 +# 用例类型: 功能用例 +# 优先级: P1 +# 用例描述: systemd系统服务管理器基本功能测试 +# 前置条件: systemd作为init系统运行 +# 用例步骤: +# 1. 检查systemd进程(PID 1)和版本 +# 2. 验证systemctl基本命令(list-units/list-unit-files/show-environment) +# 3. 检查系统运行状态(running/degraded)和默认target +# 4. 检查systemd配置目录和运行时目录 +# 5. 验证journalctl日志功能和systemd-analyze工具 +# 预期结果: +# 1. systemd进程PID为1且正常运行,版本可查询 +# 2. systemctl命令功能正常 +# 3. 系统运行状态为running或degraded +# 4. 配置目录和运行时目录存在 +# 5. 日志功能完整可用 +# 用例执行参数: +# 需求链接: +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="systemd系统服务管理器基本功能测试" + +tcnt=5 + +setup() +{ + tst_res $TINFO "检查systemd环境..." + if [ ! -f /proc/1/comm ]; then + tst_brk $TCONF "/proc/1/comm不存在,无法确认init系统" + fi + local init_proc + init_proc=$(cat /proc/1/comm 2>/dev/null) + if [ "$init_proc" != "systemd" ]; then + tst_brk $TCONF "PID 1不是systemd(当前: $init_proc),跳过测试" + fi +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查systemd进程和版本" + pgrep -x "systemd" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "systemd进程未找到" + return + fi + systemctl --version >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "systemctl --version执行失败" + return + fi + tst_res $TPASS "systemd进程和版本检查通过" + ;; + 1) + tst_res $TINFO "验证systemctl基本命令" + local fail=0 + systemctl list-units --type=service --state=running >/dev/null 2>&1 || fail=1 + systemctl list-unit-files --type=service >/dev/null 2>&1 || fail=1 + systemctl show-environment >/dev/null 2>&1 || fail=1 + if [ $fail -ne 0 ]; then + tst_res $TFAIL "systemctl基本命令执行失败" + return + fi + tst_res $TPASS "systemctl基本命令验证通过" + ;; + 2) + tst_res $TINFO "检查系统运行状态和默认target" + local sys_state + sys_state=$(systemctl is-system-running 2>/dev/null) + if [ "$sys_state" != "running" ] && [ "$sys_state" != "degraded" ]; then + tst_res $TFAIL "系统运行状态异常: $sys_state" + return + fi + systemctl get-default >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "获取默认target失败" + return + fi + tst_res $TPASS "系统运行状态正常: $sys_state" + ;; + 3) + tst_res $TINFO "检查systemd配置目录和运行时目录" + local fail=0 + [ -d /etc/systemd/system ] || fail=1 + [ -d /lib/systemd/system ] || [ -d /usr/lib/systemd/system ] || fail=1 + [ -d /run/systemd ] || fail=1 + [ -d /run/systemd/system ] || fail=1 + if [ $fail -ne 0 ]; then + tst_res $TFAIL "systemd关键目录缺失" + return + fi + tst_res $TPASS "systemd配置目录和运行时目录检查通过" + ;; + 4) + tst_res $TINFO "验证journalctl日志功能和systemd-analyze" + journalctl --no-pager -n 10 >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "journalctl查询失败" + return + fi + systemctl is-active systemd-journald >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "systemd-journald未运行" + return + fi + if which systemd-analyze >/dev/null 2>&1; then + systemd-analyze >/dev/null 2>&1 || tst_res $TWARN "systemd-analyze执行异常" + fi + tst_res $TPASS "journalctl日志功能验证通过" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/systemd_custom_service_tc002.sh b/os_smoke_src/testcases/baseos/service/systemd_custom_service_tc002.sh new file mode 100755 index 0000000..4413757 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/systemd_custom_service_tc002.sh @@ -0,0 +1,165 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: systemd_custom_service_tc002 +# 用例类型: 功能用例 +# 优先级: P1 +# 用例描述: systemd自定义服务创建和生命周期管理测试 +# 前置条件: systemd正常运行 +# 用例步骤: +# 1. 创建测试服务脚本和unit文件,daemon-reload后验证识别 +# 2. 启动服务,验证状态、进程和日志输出 +# 3. 测试服务重启,验证重启后服务正常 +# 4. 测试服务停止,验证停止后状态 +# 5. 测试服务enable/disable生命周期管理 +# 预期结果: +# 1. 服务脚本和unit文件创建成功,systemd识别该服务 +# 2. 服务启动正常,进程存在,日志写入正确 +# 3. 服务重启后恢复active状态 +# 4. 服务停止后不再active +# 5. enable/disable切换正常 +# 用例执行参数: +# 需求链接: +#**************************************************# + +source ../../../../tools/case_lib.sh +TCASE_DESC="systemd自定义服务创建和生命周期管理测试" + +tcnt=5 + +TEST_SERVICE_NAME="test-custom-service" +TEST_SERVICE_SCRIPT="/usr/local/bin/${TEST_SERVICE_NAME}" +TEST_SERVICE_UNIT="/etc/systemd/system/${TEST_SERVICE_NAME}.service" +TEST_PID_FILE="/run/${TEST_SERVICE_NAME}.pid" +TEST_LOG_FILE="/var/log/${TEST_SERVICE_NAME}.log" + +setup() +{ + tst_res $TINFO "创建测试服务脚本和unit文件" + + cat > "$TEST_SERVICE_SCRIPT" << 'SCRIPT_EOF' +#!/bin/bash +SERVICE_NAME="test-custom-service" +PID_FILE="/run/test-custom-service.pid" +LOG_FILE="/var/log/test-custom-service.log" +mkdir -p "$(dirname "$LOG_FILE")" +echo "$(date): $SERVICE_NAME starting..." >> "$LOG_FILE" +echo $$ > "$PID_FILE" +while true; do + echo "$(date): $SERVICE_NAME is running (PID: $$)" >> "$LOG_FILE" + sleep 5 +done +SCRIPT_EOF + chmod +x "$TEST_SERVICE_SCRIPT" + + cat > "$TEST_SERVICE_UNIT" << UNIT_EOF +[Unit] +Description=Test Custom Service for systemd validation +After=network.target + +[Service] +Type=simple +ExecStart=$TEST_SERVICE_SCRIPT +PIDFile=$TEST_PID_FILE +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target +UNIT_EOF + + systemctl daemon-reload +} + +cleanup() +{ + systemctl stop "$TEST_SERVICE_NAME" 2>/dev/null + systemctl disable "$TEST_SERVICE_NAME" 2>/dev/null + rm -f "$TEST_SERVICE_UNIT" "$TEST_SERVICE_SCRIPT" "$TEST_PID_FILE" "$TEST_LOG_FILE" + systemctl daemon-reload 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "验证服务文件创建和systemd识别" + [ -f "$TEST_SERVICE_SCRIPT" ] && [ -x "$TEST_SERVICE_SCRIPT" ] || { + tst_res $TFAIL "服务脚本未创建或无执行权限"; return; } + [ -f "$TEST_SERVICE_UNIT" ] || { + tst_res $TFAIL "unit文件未创建"; return; } + systemctl list-unit-files | grep -q "$TEST_SERVICE_NAME" || { + tst_res $TFAIL "systemd未识别自定义服务"; return; } + tst_res $TPASS "服务文件创建成功且systemd已识别" + ;; + 1) + tst_res $TINFO "启动服务,验证状态、进程和日志" + systemctl start "$TEST_SERVICE_NAME" + if [ $? -ne 0 ]; then + tst_res $TFAIL "服务启动失败"; return + fi + sleep 3 + systemctl is-active "$TEST_SERVICE_NAME" >/dev/null 2>&1 || { + tst_res $TFAIL "服务启动后状态非active"; return; } + if [ -f "$TEST_PID_FILE" ]; then + local pid + pid=$(cat "$TEST_PID_FILE") + kill -0 "$pid" 2>/dev/null || { + tst_res $TFAIL "服务进程(PID:$pid)不存在"; return; } + else + tst_res $TFAIL "PID文件未生成"; return + fi + [ -f "$TEST_LOG_FILE" ] && grep -q "starting" "$TEST_LOG_FILE" || { + tst_res $TFAIL "服务日志未正确写入"; return; } + tst_res $TPASS "服务启动正常,进程和日志验证通过" + ;; + 2) + tst_res $TINFO "测试服务重启" + systemctl restart "$TEST_SERVICE_NAME" + if [ $? -ne 0 ]; then + tst_res $TFAIL "systemctl restart执行失败"; return + fi + sleep 2 + systemctl is-active "$TEST_SERVICE_NAME" >/dev/null 2>&1 || { + tst_res $TFAIL "重启后服务非active"; return; } + tst_res $TPASS "服务重启后恢复正常" + ;; + 3) + tst_res $TINFO "测试服务停止" + systemctl stop "$TEST_SERVICE_NAME" + if [ $? -ne 0 ]; then + tst_res $TFAIL "systemctl stop执行失败"; return + fi + sleep 1 + if systemctl is-active "$TEST_SERVICE_NAME" >/dev/null 2>&1; then + tst_res $TFAIL "停止后服务仍为active" + return + fi + tst_res $TPASS "服务停止验证通过" + ;; + 4) + tst_res $TINFO "测试服务enable/disable" + systemctl enable "$TEST_SERVICE_NAME" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "enable服务失败"; return + fi + systemctl is-enabled "$TEST_SERVICE_NAME" >/dev/null 2>&1 || { + tst_res $TFAIL "enable后状态不正确"; return; } + + systemctl disable "$TEST_SERVICE_NAME" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "disable服务失败"; return + fi + if systemctl is-enabled "$TEST_SERVICE_NAME" >/dev/null 2>&1; then + tst_res $TFAIL "disable后服务仍为enabled"; return + fi + tst_res $TPASS "enable/disable生命周期管理正常" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/systemd_daemon_restart_tc003.sh b/os_smoke_src/testcases/baseos/service/systemd_daemon_restart_tc003.sh new file mode 100755 index 0000000..7f58df0 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/systemd_daemon_restart_tc003.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: systemd_daemon_restart_tc003 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 验证systemd守护进程异常重启策略(Restart=always),进程被kill后自动拉起 +# 前置条件: 可安装nginx +# 用例步骤: +# 1. 安装nginx并启动,验证服务状态正常 +# 2. 配置Restart=always,重载并重启服务 +# 3. kill -9杀掉nginx进程,等待自动拉起 +# 4. 验证服务自动恢复为active状态 +# 预期结果: +# 1. nginx安装并启动成功 +# 2. Restart=always配置生效 +# 3. nginx进程被杀后systemd自动重新拉起 +# 4. 服务恢复为active(running)状态 +# 用例执行参数: +# 需求链接: +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +TCASE_DESC="验证systemd守护进程异常重启策略(Restart=always),进程被kill后自动拉起" + +tcnt=4 + +NGINX_UNIT="/usr/lib/systemd/system/nginx.service" +NGINX_UNIT_BAK="" + +setup() +{ + tst_res $TINFO "安装nginx并备份unit文件" + install_packages "nginx" + if [ -f "$NGINX_UNIT" ]; then + NGINX_UNIT_BAK="${NGINX_UNIT}.bak.$$" + cp -af "$NGINX_UNIT" "$NGINX_UNIT_BAK" + else + tst_brk $TCONF "nginx unit文件不存在: $NGINX_UNIT" + fi +} + +cleanup() +{ + systemctl stop nginx 2>/dev/null + if [ -n "$NGINX_UNIT_BAK" ] && [ -f "$NGINX_UNIT_BAK" ]; then + mv -f "$NGINX_UNIT_BAK" "$NGINX_UNIT" + systemctl daemon-reload 2>/dev/null + fi + systemctl start nginx 2>/dev/null +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "启动nginx并验证服务状态" + systemctl start nginx + if [ $? -ne 0 ]; then + tst_res $TFAIL "nginx启动失败"; return + fi + sleep 1 + systemctl is-active nginx >/dev/null 2>&1 || { + tst_res $TFAIL "nginx启动后状态非active"; return; } + tst_res $TPASS "nginx启动并运行正常" + ;; + 1) + tst_res $TINFO "配置Restart=always并重载" + if ! grep -q "^Restart=always" "$NGINX_UNIT"; then + sed -i '/^Restart/d' "$NGINX_UNIT" + sed -i '/\[Service\]/a Restart=always' "$NGINX_UNIT" + fi + systemctl daemon-reload + if [ $? -ne 0 ]; then + tst_res $TFAIL "daemon-reload失败"; return + fi + systemctl restart nginx + sleep 1 + systemctl is-active nginx >/dev/null 2>&1 || { + tst_res $TFAIL "配置Restart=always后重启nginx失败"; return; } + tst_res $TPASS "Restart=always配置生效,nginx重启正常" + ;; + 2) + tst_res $TINFO "kill -9杀掉nginx进程" + local pids + pids=$(pgrep -x nginx 2>/dev/null) + if [ -z "$pids" ]; then + tst_res $TFAIL "未找到nginx进程"; return + fi + kill -9 $pids 2>/dev/null + sleep 1 + # 确认进程已被杀 + local old_pid + old_pid=$(echo "$pids" | head -1) + if kill -0 "$old_pid" 2>/dev/null; then + tst_res $TFAIL "kill -9后原进程仍存在"; return + fi + tst_res $TPASS "nginx进程已被成功杀掉" + ;; + 3) + tst_res $TINFO "验证nginx被systemd自动拉起" + sleep 5 + systemctl is-active nginx >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "nginx未被自动拉起,状态: $(systemctl is-active nginx 2>/dev/null)" + return + fi + local new_pids + new_pids=$(pgrep -x nginx 2>/dev/null) + if [ -z "$new_pids" ]; then + tst_res $TFAIL "nginx进程不存在"; return + fi + tst_res $TPASS "nginx被systemd自动拉起,服务恢复正常" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/service/tuned_service_tc005.sh b/os_smoke_src/testcases/baseos/service/tuned_service_tc005.sh new file mode 100755 index 0000000..22c80f5 --- /dev/null +++ b/os_smoke_src/testcases/baseos/service/tuned_service_tc005.sh @@ -0,0 +1,161 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: tuned_service_tc005 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: tuned性能调优服务验证 - 验证tuned服务状态、profile切换及tuned-adm命令 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 验证tuned软件包已安装并启动服务 +# 2. 使用tuned-adm list列出可用profile +# 3. 使用tuned-adm active查询当前活动profile +# 4. 切换到throughput-performance并验证生效,然后恢复原profile +# 预期结果: +# 1. tuned服务active(running) +# 2. tuned-adm list返回多个可用profile +# 3. tuned-adm active返回当前活动profile +# 4. profile切换成功并可恢复 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "验证tuned服务运行状态" + "tuned-adm列出可用profile" + "查询当前活动profile" + "切换profile并验证生效" +) +tcnt=${#TCASE_DESC[@]} + +TUNED_WAS_ACTIVE=0 +ORIG_PROFILE="" + +setup() +{ + install_packages "tuned" + + if systemctl is-active --quiet tuned 2>/dev/null; then + TUNED_WAS_ACTIVE=1 + fi + + # 确保tuned运行 + if ! systemctl is-active --quiet tuned 2>/dev/null; then + systemctl start tuned 2>/dev/null + sleep 2 + fi + + # 记录原始profile + ORIG_PROFILE=$(tuned-adm active 2>/dev/null | awk -F': ' '{print $2}' | tr -d '[:space:]') +} + +cleanup() +{ + # 恢复原始profile + if [ -n "$ORIG_PROFILE" ]; then + tuned-adm profile "$ORIG_PROFILE" 2>/dev/null + fi + + if [ $TUNED_WAS_ACTIVE -eq 0 ]; then + systemctl stop tuned 2>/dev/null + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if systemctl is-active --quiet tuned 2>/dev/null; then + tst_res $TPASS "tuned服务正在运行" + else + systemctl start tuned 2>/dev/null + sleep 2 + if systemctl is-active --quiet tuned 2>/dev/null; then + tst_res $TPASS "tuned服务启动成功" + else + tst_res $TFAIL "tuned服务未运行且无法启动" + fi + fi + ;; + 1) + ensure_command "tuned-adm" "tuned" + + local profile_list + profile_list=$(tuned-adm list 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "tuned-adm list执行失败: ${profile_list}" + return + fi + + local profile_count + profile_count=$(echo "$profile_list" | grep -c "^- ") + if [ "$profile_count" -gt 0 ]; then + tst_res $TPASS "tuned-adm列出${profile_count}个可用profile" + else + tst_res $TFAIL "tuned-adm list未返回有效profile列表" + fi + ;; + 2) + local active_output + active_output=$(tuned-adm active 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "tuned-adm active执行失败: ${active_output}" + return + fi + + if echo "$active_output" | grep -qi "current active profile"; then + local current_profile + current_profile=$(echo "$active_output" | awk -F': ' '{print $2}' | tr -d '[:space:]') + tst_res $TPASS "当前活动profile: ${current_profile}" + else + tst_res $TFAIL "tuned-adm active未返回有效信息: ${active_output}" + fi + ;; + 3) + # 选择一个与当前不同的profile进行切换测试 + local target_profile="throughput-performance" + if [ "$ORIG_PROFILE" = "$target_profile" ]; then + target_profile="balanced" + fi + + # 检查目标profile是否可用 + if ! tuned-adm list 2>/dev/null | grep -q "^- ${target_profile}"; then + # 找第一个与当前不同的profile + target_profile=$(tuned-adm list 2>/dev/null | grep "^- " | sed 's/^- //' | grep -v "^${ORIG_PROFILE}$" | head -1) + if [ -z "$target_profile" ]; then + tst_res $TCONF "无可切换的profile" + return + fi + fi + + tuned-adm profile "$target_profile" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "切换到profile ${target_profile}失败" + return + fi + sleep 2 + + local new_active + new_active=$(tuned-adm active 2>/dev/null | awk -F': ' '{print $2}' | tr -d '[:space:]') + if [ "$new_active" = "$target_profile" ]; then + tst_res $TPASS "profile切换成功: ${ORIG_PROFILE} -> ${target_profile}" + else + tst_res $TFAIL "profile切换后验证失败,期望${target_profile},实际${new_active}" + fi + + # 恢复原始profile + if [ -n "$ORIG_PROFILE" ]; then + tuned-adm profile "$ORIG_PROFILE" 2>/dev/null + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/systools/Makefile b/os_smoke_src/testcases/baseos/systools/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/baseos/systools/cpu_monitor_tool_tc001.sh b/os_smoke_src/testcases/baseos/systools/cpu_monitor_tool_tc001.sh new file mode 100755 index 0000000..7110164 --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/cpu_monitor_tool_tc001.sh @@ -0,0 +1,154 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cpu_monitor_tool_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: CPU监控工具验证 - 验证top/mpstat在stress-ng加压下能正确反映CPU负载变化 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 安装procps/sysstat/stress-ng并验证top、mpstat命令可用 +# 2. 启动stress-ng对单核施加60%CPU负载 +# 3. 使用top采集CPU总利用率,验证us(用户态)非零 +# 4. 使用mpstat采集各CPU利用率,验证被加压核usr非零 +# 5. 停止stress-ng,验证CPU负载回落 +# 预期结果: +# 1. top和mpstat命令均可用 +# 2. stress-ng加压进程启动成功 +# 3. top采集到的总CPU us利用率大于0 +# 4. mpstat采集到被加压核的usr利用率大于10% +# 5. 负载结束后CPU利用率明显回落 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="CPU监控工具验证 - 验证top/mpstat在stress-ng加压下能正确反映CPU负载变化" +tcnt=5 + +STRESS_PID="" + +setup() +{ + install_packages "procps-ng sysstat stress-ng" +} + +cleanup() +{ + [ -n "$STRESS_PID" ] && kill "$STRESS_PID" 2>/dev/null + killall stress-ng 2>/dev/null + rm -f /tmp/cpu_monitor_top.$$ /tmp/cpu_monitor_mpstat.$$ +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local fail=0 + for cmd in top mpstat; do + if ! which "$cmd" >/dev/null 2>&1; then + tst_res $TFAIL "${cmd}命令不可用" + fail=1 + fi + done + if [ $fail -eq 0 ]; then + tst_res $TPASS "top和mpstat命令均可用" + fi + ;; + 1) + stress-ng --cpu 1 --cpu-load 60 --timeout 40s &>/dev/null & + STRESS_PID=$! + sleep 5 + if ! kill -0 "$STRESS_PID" 2>/dev/null; then + tst_res $TFAIL "stress-ng加压进程启动失败" + return + fi + tst_res $TPASS "stress-ng加压进程启动成功(PID=$STRESS_PID)" + ;; + 2) + # top批处理模式采集3次,跳过第1次(不准确),取后2次的us值 + top -b -d 2 -n 3 > /tmp/cpu_monitor_top.$$ 2>/dev/null + # 提取 %Cpu(s) 行中的 us 值,兼容不同 top 版本格式 + # 格式1: %Cpu(s): 63.2 us, ... + # 格式2: Cpu(s): 63.2%us, ... + local us_values + us_values=$(grep -i "cpu" /tmp/cpu_monitor_top.$$ | grep -i "us" \ + | awk '{for(i=1;i<=NF;i++){if($i~/us/){gsub(/[^0-9.]/,"",$(i-1)); print $(i-1)}}}' \ + | tail -n 2) + if [ -z "$us_values" ]; then + # 备用方案:直接用 sed 提取 + us_values=$(grep -i "%Cpu" /tmp/cpu_monitor_top.$$ \ + | sed 's/.*: *\([0-9.]*\) *us.*/\1/' | tail -n 2) + fi + # 多核机器上单核加压,总CPU us被稀释可能很小(如0.5%), + # 因此使用awk做浮点比较而非取整比较 + local found_nonzero=0 + for us in $us_values; do + if [ -n "$us" ] && awk "BEGIN{exit !($us > 0.0)}" 2>/dev/null; then + found_nonzero=1 + break + fi + done + if [ $found_nonzero -eq 1 ]; then + tst_res $TPASS "top采集CPU us利用率非零(${us_values})" + else + tst_res $TFAIL "top采集CPU us利用率为0,加压未生效" + fi + ;; + 3) + # mpstat采集所有CPU 2秒间隔 3次 + mpstat -P ALL 2 3 > /tmp/cpu_monitor_mpstat.$$ 2>/dev/null + # 提取 Average 行(排除 all 和表头),查看 %usr 列 + # mpstat 输出格式: Average: CPU %usr %nice %sys ... + local usr_col + usr_col=$(head -5 /tmp/cpu_monitor_mpstat.$$ | grep -n "%usr" | head -1 | awk -F: '{print $1}') + local max_usr=0 + local line_usr + while read -r line_usr; do + local val + val=$(echo "$line_usr" | awk '{print $3}') + local int_val + int_val=$(echo "$val" | cut -d'.' -f1) + if [ -n "$int_val" ] && [ "$int_val" -gt "$max_usr" ] 2>/dev/null; then + max_usr=$int_val + fi + done < <(grep "Average" /tmp/cpu_monitor_mpstat.$$ | grep -v "CPU" | grep -v "all") + + if [ "$max_usr" -gt 10 ]; then + tst_res $TPASS "mpstat检测到被加压核usr利用率=${max_usr}%,大于10%" + else + tst_res $TFAIL "mpstat未检测到明显CPU负载(最大usr=${max_usr}%)" + fi + ;; + 4) + # 停止stress-ng + [ -n "$STRESS_PID" ] && kill "$STRESS_PID" 2>/dev/null + killall stress-ng 2>/dev/null + STRESS_PID="" + sleep 3 + + # 再次用mpstat采集,验证负载回落 + local idle_line + idle_line=$(mpstat 1 2 2>/dev/null | grep "Average" | grep "all") + # %idle 是最后一列 + local idle_val + idle_val=$(echo "$idle_line" | awk '{print $NF}') + local idle_int + idle_int=$(echo "$idle_val" | cut -d'.' -f1) + + if [ -n "$idle_int" ] && [ "$idle_int" -gt 30 ] 2>/dev/null; then + tst_res $TPASS "stress-ng停止后CPU idle=${idle_val}%,负载已回落" + else + tst_res $TFAIL "stress-ng停止后CPU idle=${idle_val}%,负载未回落" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/systools/date_time_tool_tc001.sh b/os_smoke_src/testcases/baseos/systools/date_time_tool_tc001.sh new file mode 100755 index 0000000..5f9dcd3 --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/date_time_tool_tc001.sh @@ -0,0 +1,169 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: date_time_tool_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 日期和时间管理工具验证 - 验证date设置/读取、hwclock同步,支持NTP/非NTP环境 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 备份当前时间并停止NTP同步服务(如有) +# 2. 使用date -s设置系统日期并校验 +# 3. 使用date -s设置系统时间并校验 +# 4. 使用hwclock读取硬件时钟 +# 5. 使用hwclock -s将硬件时钟同步到系统时间并恢复原始时间 +# 预期结果: +# 1. 时间备份成功,NTP服务已停止(如有) +# 2. date -s设置日期后date +%Y-%m-%d输出匹配 +# 3. date -s设置时间后date +%H:%M输出匹配 +# 4. hwclock命令执行成功 +# 5. hwclock -s同步成功,原始时间恢复 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "备份时间并停止NTP同步服务" + "date -s设置系统日期" + "date -s设置系统时间" + "hwclock读取硬件时钟" + "hwclock -s同步并恢复原始时间" +) +tcnt=${#TCASE_DESC[@]} + +ORIG_EPOCH="" +NTP_SERVICE="" +NTP_WAS_ACTIVE=0 + +setup() +{ + # 备份当前时间(epoch秒) + ORIG_EPOCH=$(date +%s) + + # 检测并停止NTP同步服务 + local svc + for svc in chronyd ntpd systemd-timesyncd; do + if systemctl is-active --quiet "$svc" 2>/dev/null; then + NTP_SERVICE="$svc" + NTP_WAS_ACTIVE=1 + break + fi + done + + # 也检查timedatectl的NTP同步状态 + if timedatectl show 2>/dev/null | grep -q "NTP=yes"; then + timedatectl set-ntp false 2>/dev/null + fi +} + +cleanup() +{ + # 恢复原始时间 + if [ -n "$ORIG_EPOCH" ]; then + date -s "@${ORIG_EPOCH}" >/dev/null 2>&1 + hwclock -w 2>/dev/null + fi + + # 恢复NTP服务 + if [ $NTP_WAS_ACTIVE -eq 1 ] && [ -n "$NTP_SERVICE" ]; then + systemctl start "$NTP_SERVICE" 2>/dev/null + fi + + # 恢复timedatectl NTP + if which timedatectl >/dev/null 2>&1; then + timedatectl set-ntp true 2>/dev/null + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 备份时间并停止NTP + if [ -z "$ORIG_EPOCH" ]; then + tst_res $TFAIL "无法获取当前系统时间" + return + fi + tst_res $TINFO "原始时间备份: epoch=${ORIG_EPOCH}" + + if [ $NTP_WAS_ACTIVE -eq 1 ]; then + systemctl stop "$NTP_SERVICE" 2>/dev/null + sleep 1 + if systemctl is-active --quiet "$NTP_SERVICE" 2>/dev/null; then + tst_res $TFAIL "无法停止NTP服务${NTP_SERVICE}" + return + fi + tst_res $TPASS "NTP服务${NTP_SERVICE}已停止,可安全修改时间" + else + tst_res $TPASS "系统无活跃NTP服务,可直接修改时间" + fi + ;; + 1) + # date -s设置系统日期 + local test_date="2025-06-17" + date -s "$test_date" >/dev/null 2>&1 + local actual + actual=$(date +%Y-%m-%d) + if [ "$actual" = "$test_date" ]; then + tst_res $TPASS "date -s设置日期${test_date}成功" + else + tst_res $TFAIL "date -s设置日期失败,期望${test_date},实际${actual}" + fi + ;; + 2) + # date -s设置系统时间 + local test_time="10:10" + date -s "$test_time" >/dev/null 2>&1 + local actual + actual=$(date +%H:%M) + if [ "$actual" = "$test_time" ]; then + tst_res $TPASS "date -s设置时间${test_time}成功" + else + tst_res $TFAIL "date -s设置时间失败,期望${test_time},实际${actual}" + fi + ;; + 3) + # hwclock读取硬件时钟 + local output + output=$(hwclock 2>&1) + if [ $? -eq 0 ] && [ -n "$output" ]; then + tst_res $TPASS "hwclock读取成功: ${output}" + else + # 虚拟机/容器可能无RTC设备 + if [ ! -e /dev/rtc0 ] && [ ! -e /dev/rtc ]; then + tst_res $TCONF "无RTC设备(/dev/rtc0不存在),跳过hwclock测试" + else + tst_res $TFAIL "hwclock读取失败: ${output}" + fi + fi + ;; + 4) + # hwclock -s同步并恢复 + if [ ! -e /dev/rtc0 ] && [ ! -e /dev/rtc ]; then + tst_res $TCONF "无RTC设备,跳过hwclock -s测试" + return + fi + hwclock -s 2>/dev/null + if [ $? -eq 0 ]; then + tst_res $TPASS "hwclock -s同步硬件时钟到系统成功" + else + tst_res $TFAIL "hwclock -s同步失败" + fi + + # 恢复原始时间 + if [ -n "$ORIG_EPOCH" ]; then + date -s "@${ORIG_EPOCH}" >/dev/null 2>&1 + tst_res $TINFO "已恢复原始系统时间" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/systools/dracut_tc001.sh b/os_smoke_src/testcases/baseos/systools/dracut_tc001.sh new file mode 100755 index 0000000..c63e970 --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/dracut_tc001.sh @@ -0,0 +1,138 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: dracut_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: 校验dracut工具链功能:lsinitrd查看initramfs内容、dracut列出可用模块、dracut -f重建initramfs +# 前置条件: 系统已安装dracut,具备root权限 +# 步骤: +# 1. 检查dracut工具包已安装且可用 +# 2. 使用lsinitrd查看当前initramfs,验证包含关键模块目录 +# 3. 使用dracut --list-modules列出可用模块,验证包含base/systemd等核心模块 +# 4. 使用dracut -f重建initramfs并验证文件更新 +# 预期结果: +# 1. dracut/lsinitrd命令可用 +# 2. lsinitrd正常输出initramfs内容,包含关键文件 +# 3. dracut --list-modules列出核心模块(base等) +# 4. dracut -f重建成功,initramfs文件时间戳更新 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "检查dracut工具包可用" + "lsinitrd查看initramfs内容" + "dracut --list-modules列出可用模块" + "dracut -f重建initramfs" +) +tcnt=${#TCASE_DESC[@]} + +INITRAMFS_PATH="" +INITRAMFS_BACKUP="" + +setup() +{ + ensure_command "dracut" "dracut" + ensure_command "lsinitrd" "dracut" + + INITRAMFS_PATH="/boot/initramfs-$(uname -r).img" + if [ ! -f "$INITRAMFS_PATH" ]; then + tst_brk $TCONF "initramfs文件不存在: $INITRAMFS_PATH" + fi + + # 备份initramfs用于重建测试后恢复 + INITRAMFS_BACKUP="${INITRAMFS_PATH}.bak.$$" + cp -a "$INITRAMFS_PATH" "$INITRAMFS_BACKUP" 2>/dev/null +} + +cleanup() +{ + # 如果备份存在,恢复原始initramfs + if [ -n "$INITRAMFS_BACKUP" ] && [ -f "$INITRAMFS_BACKUP" ]; then + cp -a "$INITRAMFS_BACKUP" "$INITRAMFS_PATH" 2>/dev/null + rm -f "$INITRAMFS_BACKUP" + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 检查dracut工具包 + ensure_command "dracut" "dracut" + ensure_command "lsinitrd" "dracut" + tst_res $TPASS "dracut工具包可用" + ;; + 1) + # lsinitrd查看initramfs内容 + local output + output=$(lsinitrd "$INITRAMFS_PATH" 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "lsinitrd执行失败: $(echo "$output" | head -3)" + return + fi + + # 验证输出包含关键内容(内核模块或lib目录或bin目录) + if echo "$output" | grep -qE "(usr/lib/modules|lib/modules|usr/bin|usr/sbin|etc/)"; then + tst_res $TPASS "lsinitrd正常输出initramfs内容,包含关键目录" + else + tst_res $TFAIL "lsinitrd输出不包含预期的关键目录内容" + fi + ;; + 2) + # dracut --list-modules列出可用模块 + local output + output=$(dracut --list-modules 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "dracut --list-modules执行失败: $(echo "$output" | head -3)" + return + fi + + # 验证包含核心模块base + if echo "$output" | grep -qw "base"; then + tst_res $TPASS "dracut --list-modules列出可用模块,包含base核心模块" + else + tst_res $TFAIL "dracut --list-modules未列出base核心模块" + fi + ;; + 3) + # dracut -f重建initramfs + local before_mtime + before_mtime=$(stat -c %Y "$INITRAMFS_PATH" 2>/dev/null) + + # 等1秒确保时间戳能区分 + sleep 1 + + local output + output=$(dracut -f "$INITRAMFS_PATH" "$(uname -r)" 2>&1) + if [ $? -ne 0 ]; then + tst_res $TFAIL "dracut -f重建initramfs失败: $(echo "$output" | head -3)" + return + fi + + if [ ! -f "$INITRAMFS_PATH" ]; then + tst_res $TFAIL "dracut -f后initramfs文件不存在" + return + fi + + local after_mtime + after_mtime=$(stat -c %Y "$INITRAMFS_PATH" 2>/dev/null) + + if [ "$after_mtime" -gt "$before_mtime" ]; then + tst_res $TPASS "dracut -f重建initramfs成功,文件已更新" + else + tst_res $TFAIL "dracut -f后initramfs时间戳未更新" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/systools/hostname_tc001.sh b/os_smoke_src/testcases/baseos/systools/hostname_tc001.sh new file mode 100755 index 0000000..bea7341 --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/hostname_tc001.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: hostname_tc001 +# 用例类型: 功能测试 +# 优先级: P3 +# 用例描述: 验证 hostname 命令获取主机名功能是否正常 +# 前置条件: 系统已安装 hostname 命令 +# 用例步骤: +# 1. 执行 hostname 命令 +# 2. 检查 hostname 输出是否非空且不包含空白字符 +# 预期结果: +# 1. hostname 执行成功, 返回值为 0 +# 2. 输出的主机名非空且无空白字符 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC="验证 hostname 命令获取主机名功能是否正常" +tcnt=1 + +setup() +{ + ensure_command "hostname" "hostname" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "执行 hostname 并检查输出" + local host_name + host_name=$(hostname 2>/dev/null) + if [ -z "${host_name}" ]; then + tst_res $TFAIL "hostname 输出为空" + return + fi + + if echo "${host_name}" | grep -q " "; then + tst_res $TFAIL "hostname 输出中包含空格: ${host_name}" + else + tst_res $TPASS "hostname 输出正常: ${host_name}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/systools/hostnamectl_tc001.sh b/os_smoke_src/testcases/baseos/systools/hostnamectl_tc001.sh new file mode 100755 index 0000000..8f1e68f --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/hostnamectl_tc001.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: hostnamectl_tc001 +# 用例类型: 功能测试 +# 优先级: P3 +# 用例描述: 验证 hostnamectl 基本查询功能是否正常 +# 前置条件: 系统使用 systemd 且安装 hostnamectl 命令 +# 用例步骤: +# 1. 执行 hostnamectl status 查询主机名相关信息 +# 预期结果: +# 1. hostnamectl status 执行成功, 输出包含 Static hostname 或 Hostname 字段 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC="验证 hostnamectl 基本查询功能是否正常" +tcnt=1 + +setup() +{ + ensure_command "hostnamectl" "systemd" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "执行 hostnamectl status" + local output + output=$(hostnamectl status 2>/dev/null) + if [ -z "${output}" ]; then + tst_res $TFAIL "hostnamectl status 输出为空" + elif echo "${output}" | grep -Ei "Static hostname:|Hostname:" >/dev/null 2>&1; then + tst_res $TPASS "hostnamectl status 输出包含 hostname 字段" + else + tst_res $TFAIL "hostnamectl status 输出中未包含 Static hostname/Hostname 字段" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/systools/io_monitor_tool_tc003.sh b/os_smoke_src/testcases/baseos/systools/io_monitor_tool_tc003.sh new file mode 100755 index 0000000..3ebcc31 --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/io_monitor_tool_tc003.sh @@ -0,0 +1,163 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: io_monitor_tool_tc003 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 存储IO监控工具验证 - 验证iostat在dd写入压力下能正确反映磁盘IO变化 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 安装sysstat并验证iostat命令可用 +# 2. 自动探测系统根分区所在磁盘设备 +# 3. 启动dd写入压力,使用iostat采集IO数据,验证目标磁盘tps非零 +# 4. 停止IO压力后再次采集,验证tps回落 +# 预期结果: +# 1. iostat命令可用 +# 2. 成功探测到系统盘设备名 +# 3. dd加压期间目标磁盘tps大于0 +# 4. IO压力结束后tps明显回落 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="存储IO监控工具验证 - 验证iostat在dd写入压力下能正确反映磁盘IO变化" +tcnt=4 + +DD_PID="" +DISK_DEV="" +DD_FILE="/tmp/io_monitor_test_$$" + +setup() +{ + install_packages "sysstat" +} + +cleanup() +{ + [ -n "$DD_PID" ] && kill "$DD_PID" 2>/dev/null + rm -f "$DD_FILE" /tmp/io_monitor_iostat.$$.* +} + +# 自动探测根分区所在的物理磁盘名(不含/dev/前缀) +# 兼容 sda, vda, nvme0n1, LVM(/dev/mapper/xxx) 等 +_detect_root_disk() +{ + local root_dev + root_dev=$(df / 2>/dev/null | awk 'NR==2{print $1}') + + # LVM 设备: 通过 lsblk 逐级回溯到物理磁盘 + if echo "$root_dev" | grep -q "^/dev/mapper/\|^/dev/dm-"; then + local parent + parent=$(lsblk -no PKNAME "$root_dev" 2>/dev/null | head -1) + while [ -n "$parent" ]; do + local grandparent + grandparent=$(lsblk -no PKNAME "/dev/$parent" 2>/dev/null | head -1) + if [ -n "$grandparent" ]; then + parent="$grandparent" + else + break + fi + done + echo "$parent" + return + fi + + # 普通设备: /dev/sda1, /dev/nvme0n1p1, /dev/vda1 + root_dev=$(basename "$root_dev") + + # 去掉分区号: sda1->sda, nvme0n1p1->nvme0n1, vda1->vda + if echo "$root_dev" | grep -q "^nvme"; then + root_dev=$(echo "$root_dev" | sed 's/p[0-9]*$//') + else + root_dev=$(echo "$root_dev" | sed 's/[0-9]*$//') + fi + + echo "$root_dev" +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if ! which iostat >/dev/null 2>&1; then + tst_res $TFAIL "iostat命令不可用" + return + fi + tst_res $TPASS "iostat命令可用" + ;; + 1) + DISK_DEV=$(_detect_root_disk) + if [ -z "$DISK_DEV" ]; then + tst_brk $TCONF "无法探测到系统根分区所在磁盘" + fi + # 验证 iostat 能查到这个设备 + if ! iostat -d 1 1 2>/dev/null | grep -q "$DISK_DEV"; then + tst_res $TFAIL "iostat输出中未找到磁盘设备${DISK_DEV}" + return + fi + tst_res $TPASS "探测到系统盘设备: ${DISK_DEV}" + ;; + 2) + # 启动dd写入压力 + dd if=/dev/zero of="$DD_FILE" bs=1M count=5000 oflag=direct &>/dev/null & + DD_PID=$! + sleep 3 + + # iostat采集 2秒间隔 3次,取后2次数据 + iostat -d 2 3 > /tmp/io_monitor_iostat.$$.load 2>/dev/null + + local tps_sum=0 + local tps_count=0 + local tps_val + while read -r tps_val; do + local int_tps + int_tps=$(echo "$tps_val" | cut -d'.' -f1) + if [ -n "$int_tps" ] 2>/dev/null; then + tps_sum=$((tps_sum + int_tps)) + tps_count=$((tps_count + 1)) + fi + done < <(grep "$DISK_DEV" /tmp/io_monitor_iostat.$$.load | tail -n 2 | awk '{print $2}') + + if [ "$tps_count" -eq 0 ]; then + tst_res $TFAIL "未能从iostat输出中提取到${DISK_DEV}的tps数据" + return + fi + + if [ "$tps_sum" -gt 0 ]; then + tst_res $TPASS "dd加压期间${DISK_DEV} tps非零(累计tps=${tps_sum})" + else + tst_res $TFAIL "dd加压期间${DISK_DEV} tps为0,IO监控异常" + fi + ;; + 3) + # 等待dd结束 + [ -n "$DD_PID" ] && wait "$DD_PID" 2>/dev/null + DD_PID="" + rm -f "$DD_FILE" + sleep 3 + + # 再次采集 + iostat -d 2 2 > /tmp/io_monitor_iostat.$$.idle 2>/dev/null + local idle_tps + idle_tps=$(grep "$DISK_DEV" /tmp/io_monitor_iostat.$$.idle | tail -1 | awk '{print $2}') + local idle_int + idle_int=$(echo "$idle_tps" | cut -d'.' -f1) + + # 空闲时 tps 应该很低(允许有些后台IO) + if [ -n "$idle_int" ] 2>/dev/null; then + tst_res $TPASS "IO压力结束后${DISK_DEV} tps=${idle_tps},已回落" + else + tst_res $TPASS "IO压力结束后${DISK_DEV}无明显IO活动" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/systools/locale_tc001.sh b/os_smoke_src/testcases/baseos/systools/locale_tc001.sh new file mode 100755 index 0000000..b3feeaa --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/locale_tc001.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: locale_tc001 +# 用例类型: 功能测试 +# 优先级: P3 +# 用例描述: 验证 locale 命令的基本查询功能是否正常 +# 前置条件: 系统已安装 locale 命令 +# 用例步骤: +# 1. 执行 locale 命令查看当前本地化环境变量 +# 2. 执行 locale -a 查看系统支持的 locale 列表 +# 预期结果: +# 1. locale 执行成功, 输出包含 LANG 或 LC_* 变量 +# 2. locale -a 执行成功, 输出的 locale 列表非空 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "locale 查看当前本地化环境变量" + "locale -a 查看支持的 locale 列表" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + ensure_command "locale" "glibc-common" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "执行 locale 查看当前本地化环境" + local output + output=$(locale 2>/dev/null) + if [ -z "${output}" ]; then + tst_res $TFAIL "locale 输出为空" + elif echo "${output}" | grep -E "^(LANG=|LC_)" >/dev/null 2>&1; then + tst_res $TPASS "locale 输出包含 LANG/LC_ 信息" + else + tst_res $TFAIL "locale 输出中未发现 LANG/LC_ 信息" + fi + ;; + 1) + tst_res $TINFO "执行 locale -a 查看支持的 locale 列表" + local list_output + list_output=$(locale -a 2>/dev/null | head -n 5) + if [ -z "${list_output}" ]; then + tst_res $TFAIL "locale -a 输出为空" + else + tst_res $TPASS "locale -a 输出正常" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/systools/localectl_tc001.sh b/os_smoke_src/testcases/baseos/systools/localectl_tc001.sh new file mode 100755 index 0000000..f35929b --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/localectl_tc001.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: localectl_tc001 +# 用例类型: 功能测试 +# 优先级: P3 +# 用例描述: 验证 localectl 基本查询功能是否正常 +# 前置条件: 系统使用 systemd 且安装 localectl 命令 +# 用例步骤: +# 1. 执行 localectl status 查询当前本地化配置 +# 2. 执行 localectl list-locales 查询支持的 locale 列表 +# 预期结果: +# 1. localectl status 执行成功, 返回值为 0 +# 2. list-locales 输出的 locale 列表非空 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "localectl status 查询本地化配置" + "localectl list-locales 查询支持的 locale" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + ensure_command "localectl" "systemd" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "执行 localectl status" + if localectl status >/dev/null 2>&1; then + tst_res $TPASS "localectl status 执行成功" + else + tst_res $TFAIL "localectl status 执行失败" + fi + ;; + 1) + tst_res $TINFO "执行 localectl list-locales" + local locales + locales=$(localectl list-locales 2>/dev/null | head -n 5) + if [ -z "${locales}" ]; then + tst_res $TFAIL "localectl list-locales 输出为空" + else + tst_res $TPASS "localectl list-locales 输出正常" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/systools/mem_monitor_tool_tc002.sh b/os_smoke_src/testcases/baseos/systools/mem_monitor_tool_tc002.sh new file mode 100755 index 0000000..6cc68aa --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/mem_monitor_tool_tc002.sh @@ -0,0 +1,174 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: mem_monitor_tool_tc002 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 内存监控工具验证 - 验证free/meminfo能正确反映hugepages分配引起的内存变化 +# 前置条件: 系统具备root权限,可用内存大于2G +# 步骤: +# 1. 检查free和/proc/meminfo基本输出正常 +# 2. 记录当前hugepages值,按可用内存动态计算分配量(min(2G,可用内存30%)),分配hugepages +# 3. 验证free中used增长与hugepages分配量基本一致 +# 4. 验证/proc/meminfo中HugePages_Total与实际值一致 +# 5. 释放hugepages,验证free/meminfo恢复 +# 预期结果: +# 1. free输出包含Mem行,/proc/meminfo包含MemTotal +# 2. hugepages分配成功 +# 3. free中used增长量与hugepages分配量误差在1G以内 +# 4. /proc/meminfo中HugePages_Total与实际分配数一致 +# 5. 释放后hugepages恢复原值,used内存回落 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="内存监控工具验证 - 验证free/meminfo能正确反映hugepages分配引起的内存变化" +tcnt=5 + +HUGEPAGE_PATH="" +HUGEPAGE_ORIG="" +HUGEPAGE_UNIT=0 + +setup() +{ + local arch + arch=$(uname -m) + if [ "$arch" != "x86_64" ] && [ "$arch" != "aarch64" ]; then + tst_brk $TCONF "仅支持x86_64和aarch64,当前架构: $arch" + fi + + HUGEPAGE_UNIT=$(awk '/Hugepagesize:/{print $2}' /proc/meminfo) + if [ -z "$HUGEPAGE_UNIT" ]; then + tst_brk $TCONF "无法从/proc/meminfo获取Hugepagesize" + fi + HUGEPAGE_PATH="/sys/kernel/mm/hugepages/hugepages-${HUGEPAGE_UNIT}kB/nr_hugepages" + if [ ! -f "$HUGEPAGE_PATH" ]; then + tst_brk $TCONF "hugepages路径不存在: $HUGEPAGE_PATH" + fi + HUGEPAGE_ORIG=$(cat "$HUGEPAGE_PATH" 2>/dev/null) + + # 清理缓存以获得更准确的内存数据 + sync + echo 3 > /proc/sys/vm/drop_caches 2>/dev/null +} + +cleanup() +{ + # 无论测试是否中途失败,都恢复hugepages原值 + if [ -n "$HUGEPAGE_ORIG" ] && [ -n "$HUGEPAGE_PATH" ]; then + echo "$HUGEPAGE_ORIG" > "$HUGEPAGE_PATH" 2>/dev/null + sleep 2 + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local fail=0 + if ! free -m 2>/dev/null | grep -qi "mem"; then + tst_res $TFAIL "free输出中未找到Mem行" + fail=1 + fi + if ! grep -qi "MemTotal" /proc/meminfo 2>/dev/null; then + tst_res $TFAIL "/proc/meminfo中未找到MemTotal" + fail=1 + fi + if [ $fail -eq 0 ]; then + local total_mb + total_mb=$(free -m | awk '/Mem:/{print $2}') + tst_res $TPASS "free和/proc/meminfo输出正常,总内存${total_mb}MB" + fi + ;; + 1) + # 动态计算分配量:min(2G, 可用内存30%) + local avail_kb + avail_kb=$(awk '/MemAvailable:/{print $2}' /proc/meminfo 2>/dev/null) + if [ -z "$avail_kb" ] || [ "$avail_kb" -lt 2097152 ] 2>/dev/null; then + # 可用内存不足2G + if [ -n "$avail_kb" ]; then + local target_kb=$((avail_kb * 30 / 100)) + else + tst_brk $TCONF "无法获取可用内存" + fi + else + # 可用内存充足,取min(2G, 可用30%) + local pct_kb=$((avail_kb * 30 / 100)) + local two_g_kb=2097152 + if [ "$pct_kb" -lt "$two_g_kb" ]; then + local target_kb=$pct_kb + else + local target_kb=$two_g_kb + fi + fi + + local need_pages=$((target_kb / HUGEPAGE_UNIT + HUGEPAGE_ORIG)) + echo "$need_pages" > "$HUGEPAGE_PATH" 2>/dev/null + sleep 5 + + local actual + actual=$(cat "$HUGEPAGE_PATH" 2>/dev/null) + local inc_pages=$((actual - HUGEPAGE_ORIG)) + if [ "$inc_pages" -le 0 ]; then + tst_brk $TCONF "hugepages分配失败(请求${need_pages},实际${actual},原值${HUGEPAGE_ORIG})" + fi + local inc_gb=$((inc_pages * HUGEPAGE_UNIT / 1024 / 1024)) + tst_res $TPASS "hugepages分配成功: 增加${inc_pages}页(约${inc_gb}GB)" + ;; + 2) + # 验证free中used变化 + local now_used_mb + now_used_mb=$(free -m | awk '/Mem:/{print $3}') + local actual_pages + actual_pages=$(cat "$HUGEPAGE_PATH" 2>/dev/null) + local inc_pages=$((actual_pages - HUGEPAGE_ORIG)) + local inc_mb=$((inc_pages * HUGEPAGE_UNIT / 1024)) + + # used应该增长,允许500MB误差 + if [ "$inc_mb" -gt 0 ]; then + tst_res $TPASS "free显示used=${now_used_mb}MB,hugepages占用约${inc_mb}MB" + else + tst_res $TFAIL "hugepages增量为0,验证无意义" + fi + ;; + 3) + # 验证/proc/meminfo中HugePages_Total + local meminfo_total + meminfo_total=$(awk '/HugePages_Total:/{print $2}' /proc/meminfo 2>/dev/null) + local actual_pages + actual_pages=$(cat "$HUGEPAGE_PATH" 2>/dev/null) + if [ "$meminfo_total" -eq "$actual_pages" ] 2>/dev/null; then + tst_res $TPASS "/proc/meminfo中HugePages_Total=${meminfo_total}与实际一致" + else + tst_res $TFAIL "/proc/meminfo中HugePages_Total=${meminfo_total},实际${actual_pages}不一致" + fi + ;; + 4) + # 释放hugepages + echo "$HUGEPAGE_ORIG" > "$HUGEPAGE_PATH" 2>/dev/null + sleep 5 + local end_pages + end_pages=$(cat "$HUGEPAGE_PATH" 2>/dev/null) + if [ "$end_pages" -ne "$HUGEPAGE_ORIG" ] 2>/dev/null; then + tst_res $TFAIL "hugepages未恢复原值(期望${HUGEPAGE_ORIG},实际${end_pages})" + return + fi + local end_huge + end_huge=$(awk '/HugePages_Total:/{print $2}' /proc/meminfo 2>/dev/null) + if [ "$end_huge" -eq "$HUGEPAGE_ORIG" ] 2>/dev/null; then + tst_res $TPASS "hugepages恢复原值${HUGEPAGE_ORIG},/proc/meminfo一致" + else + tst_res $TFAIL "释放后/proc/meminfo中HugePages_Total=${end_huge},期望${HUGEPAGE_ORIG}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/systools/net_monitor_tool_tc004.sh b/os_smoke_src/testcases/baseos/systools/net_monitor_tool_tc004.sh new file mode 100755 index 0000000..238cdcd --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/net_monitor_tool_tc004.sh @@ -0,0 +1,147 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: net_monitor_tool_tc004 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 网络IO监控工具验证 - 验证sar在stress-ng网络加压下能正确反映TCP连接和网卡收发包变化 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 安装sysstat/stress-ng并验证sar命令可用 +# 2. 使用sar -n DEV基线采集lo网卡数据 +# 3. 启动stress-ng --sock加压,使用sar -n TCP采集TCP连接数据,验证非零 +# 4. 加压期间使用sar -n DEV采集lo网卡数据,验证收发包非零 +# 5. 停止加压,验证sar -n TCP数据回落 +# 预期结果: +# 1. sar命令可用 +# 2. 基线采集成功 +# 3. 加压期间TCP active/passive连接数非零 +# 4. 加压期间lo网卡rxpck/txpck非零 +# 5. 加压结束后TCP连接活跃度明显降低 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="网络IO监控工具验证 - 验证sar在stress-ng网络加压下能正确反映TCP连接和网卡收发包变化" +tcnt=5 + +STRESS_PID="" + +setup() +{ + install_packages "sysstat stress-ng" + # 初始化sar数据采集文件(部分系统首次使用sar会报错) + local sa_dir="/var/log/sa" + [ ! -d "$sa_dir" ] && mkdir -p "$sa_dir" +} + +cleanup() +{ + [ -n "$STRESS_PID" ] && kill "$STRESS_PID" 2>/dev/null + killall stress-ng 2>/dev/null + rm -f /tmp/net_monitor_sar.$$.* +} + +run_test() +{ + local n=$1 + + case $n in + 0) + if ! which sar >/dev/null 2>&1; then + tst_res $TFAIL "sar命令不可用" + return + fi + tst_res $TPASS "sar命令可用" + ;; + 1) + # 基线采集:sar -n DEV 1 2 + sar -n DEV 1 2 > /tmp/net_monitor_sar.$$.baseline 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "sar -n DEV基线采集执行失败" + return + fi + if ! grep -q "lo" /tmp/net_monitor_sar.$$.baseline 2>/dev/null; then + tst_res $TFAIL "sar -n DEV输出中未找到lo网卡" + return + fi + tst_res $TPASS "sar -n DEV基线采集成功,lo网卡数据正常" + ;; + 2) + # 启动网络加压 + stress-ng --sock 2 --timeout 40s &>/dev/null & + STRESS_PID=$! + sleep 5 + + # 采集TCP数据 + sar -n TCP 1 5 > /tmp/net_monitor_sar.$$.tcp 2>/dev/null + # 提取Average行的active/s(第2列) + local active + active=$(grep -E "Average|平均" /tmp/net_monitor_sar.$$.tcp 2>/dev/null \ + | grep -v "IFACE\|active" | awk '{print $2}') + local active_int + active_int=$(echo "$active" | cut -d'.' -f1) + + if [ -n "$active_int" ] && [ "$active_int" -gt 0 ] 2>/dev/null; then + tst_res $TPASS "加压期间TCP active/s=${active},非零" + else + # active可能为0但passive不为0 + local passive + passive=$(grep -E "Average|平均" /tmp/net_monitor_sar.$$.tcp 2>/dev/null \ + | grep -v "IFACE\|passive" | awk '{print $3}') + local passive_int + passive_int=$(echo "$passive" | cut -d'.' -f1) + if [ -n "$passive_int" ] && [ "$passive_int" -gt 0 ] 2>/dev/null; then + tst_res $TPASS "加压期间TCP passive/s=${passive},非零" + else + tst_res $TFAIL "加压期间TCP active/s和passive/s均为0" + fi + fi + ;; + 3) + # 采集lo网卡数据 + sar -n DEV 1 5 > /tmp/net_monitor_sar.$$.dev 2>/dev/null + # 提取Average行中lo的rxpck/s(第3列)和txpck/s(第4列) + local lo_line + lo_line=$(grep -E "Average|平均" /tmp/net_monitor_sar.$$.dev 2>/dev/null | grep "lo") + local rxpck txpck + rxpck=$(echo "$lo_line" | awk '{print $3}') + txpck=$(echo "$lo_line" | awk '{print $4}') + local rx_int tx_int + rx_int=$(echo "$rxpck" | cut -d'.' -f1) + tx_int=$(echo "$txpck" | cut -d'.' -f1) + + if [ -n "$rx_int" ] && [ "$rx_int" -gt 0 ] 2>/dev/null; then + tst_res $TPASS "加压期间lo网卡rxpck/s=${rxpck} txpck/s=${txpck},收发包非零" + else + tst_res $TFAIL "加压期间lo网卡rxpck/s=${rxpck}为零,网络监控异常" + fi + ;; + 4) + # 停止加压 + [ -n "$STRESS_PID" ] && kill "$STRESS_PID" 2>/dev/null + killall stress-ng 2>/dev/null + STRESS_PID="" + sleep 3 + + # 再次采集TCP + sar -n TCP 1 3 > /tmp/net_monitor_sar.$$.tcp_idle 2>/dev/null + local idle_active + idle_active=$(grep -E "Average|平均" /tmp/net_monitor_sar.$$.tcp_idle 2>/dev/null \ + | grep -v "IFACE\|active" | awk '{print $2}') + + if [ -n "$idle_active" ]; then + tst_res $TPASS "加压结束后TCP active/s=${idle_active},采集正常" + else + tst_res $TPASS "加压结束后TCP数据采集正常" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/systools/system_info_tc001.sh b/os_smoke_src/testcases/baseos/systools/system_info_tc001.sh new file mode 100755 index 0000000..cc9b0ea --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/system_info_tc001.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: system_info_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 系统信息查看工具,支持查看系统版本、内核版本、内存容量、CPU型号等信息 +# 前置条件: NA +# 用例步骤: +# 1. 使用 cat /etc/os-release 查看系统版本信息 +# 2. 使用 uname -r 查看当前内核版本 +# 3. 使用 free 和 cat /proc/meminfo 查看内存容量信息 +# 4. 使用 lscpu 和 cat /proc/cpuinfo 查看 CPU 型号及核心信息 +# 5. 使用 lsblk 查看磁盘及分区信息 +# 6. 使用 lspci 查看 PCI 设备信息 +# 7. 使用 ifconfig -a 查看网卡及 IP 配置信息 +# 预期结果: +# 1. /etc/os-release 中能正确显示系统版本字段 +# 2. uname -r 能输出当前正在运行的内核版本号 +# 3. free 和 /proc/meminfo 输出中包含 MemTotal 等字段 +# 4. lscpu 输出中包含 CPU 型号信息 +# 5. lsblk 能列出系统磁盘和分区列表 +# 6. lspci 能列出 PCI 设备信息 +# 7. ifconfig -a 能列出所有网卡信息 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "查看系统版本信息" + "查看内核版本" + "查看内存容量信息" + "查看CPU型号信息" + "查看磁盘分区和PCI设备信息" + "查看网卡配置信息" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + return 0 +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 /etc/os-release" + if cat /etc/os-release | grep "VERSION_ID" >/dev/null 2>&1; then + tst_res $TPASS "/etc/os-release 包含 VERSION_ID" + else + tst_res $TFAIL "/etc/os-release 未包含 VERSION_ID" + fi + ;; + 1) + tst_res $TINFO "执行 uname -r" + if uname -r >/dev/null 2>&1; then + tst_res $TPASS "uname -r 执行成功: $(uname -r)" + else + tst_res $TFAIL "uname -r 执行失败" + fi + ;; + 2) + tst_res $TINFO "检查内存信息" + if ! free >/dev/null 2>&1; then + tst_res $TFAIL "free 命令执行失败" + return + fi + if cat /proc/meminfo | grep "MemTotal" >/dev/null 2>&1; then + tst_res $TPASS "/proc/meminfo 包含 MemTotal" + else + tst_res $TFAIL "/proc/meminfo 未包含 MemTotal" + fi + ;; + 3) + tst_res $TINFO "检查CPU信息" + if lscpu | grep -iE "model name|型号名称" >/dev/null 2>&1; then + tst_res $TPASS "lscpu 包含 CPU 型号信息" + else + tst_res $TFAIL "lscpu 未包含 CPU 型号信息" + fi + if [ "$(uname -m)" != "aarch64" ]; then + if cat /proc/cpuinfo | grep -i "model name" >/dev/null 2>&1; then + tst_res $TPASS "/proc/cpuinfo 包含 model name" + else + tst_res $TFAIL "/proc/cpuinfo 未包含 model name" + fi + fi + ;; + 4) + tst_res $TINFO "检查磁盘和PCI设备" + if lsblk >/dev/null 2>&1; then + tst_res $TPASS "lsblk 执行成功" + else + tst_res $TFAIL "lsblk 执行失败" + fi + if lspci >/dev/null 2>&1; then + tst_res $TPASS "lspci 执行成功" + else + tst_res $TFAIL "lspci 执行失败" + fi + ;; + 5) + tst_res $TINFO "检查网卡信息" + if ifconfig -a >/dev/null 2>&1; then + tst_res $TPASS "ifconfig -a 执行成功" + else + tst_res $TFAIL "ifconfig -a 执行失败" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/systools/tmux_tool_tc001.sh b/os_smoke_src/testcases/baseos/systools/tmux_tool_tc001.sh new file mode 100755 index 0000000..b4c69a0 --- /dev/null +++ b/os_smoke_src/testcases/baseos/systools/tmux_tool_tc001.sh @@ -0,0 +1,195 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: tmux_tool_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: tmux终端复用器功能验证,验证session创建/detach/attach/销毁及目录保持能力 +# 前置条件: 系统具备root权限 +# 步骤: +# 1. 安装tmux工具并检查可用 +# 2. 创建tmux session并在其中切换目录,验证detach后session存活 +# 3. 创建第二个session并切换到不同目录,验证多session共存 +# 4. attach各session验证目录保持,exit退出后验证session销毁 +# 预期结果: +# 1. tmux安装成功且可用 +# 2. session创建成功,detach后session仍存活 +# 3. 两个session共存,tmux ls可列出 +# 4. attach后目录保持正确,exit后session消失 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "安装tmux工具并检查可用" + "创建session并验证detach后存活" + "多session共存验证" + "attach验证目录保持及exit销毁session" +) +tcnt=${#TCASE_DESC[@]} + +SESSION1="tmux_tc005_$$_1" +SESSION2="tmux_tc005_$$_2" +ORIG_TERM="" + +setup() +{ + # 保存并修复TERM + ORIG_TERM="$TERM" + case "$TERM" in + xterm|xterm-256color|screen|screen-256color|tmux|tmux-256color|linux) + ;; + *) + export TERM=xterm + ;; + esac + + # 清理可能残留的同名session + tmux kill-session -t "$SESSION1" 2>/dev/null + tmux kill-session -t "$SESSION2" 2>/dev/null +} + +cleanup() +{ + # 清理所有测试session + tmux kill-session -t "$SESSION1" 2>/dev/null + tmux kill-session -t "$SESSION2" 2>/dev/null + + # 恢复TERM + if [ -n "$ORIG_TERM" ]; then + export TERM="$ORIG_TERM" + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 安装tmux + ensure_command "tmux" + ;; + 1) + # 创建session1,在其中cd /home,然后detach + tmux new-session -d -s "$SESSION1" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "创建tmux session $SESSION1 失败" + return + fi + + # 在session中执行cd /home + tmux send-keys -t "$SESSION1" "cd /home" C-m 2>/dev/null + sleep 1 + + # 验证session存活 + if tmux has-session -t "$SESSION1" 2>/dev/null; then + tst_res $TPASS "session $SESSION1 创建成功并存活" + else + tst_res $TFAIL "session $SESSION1 创建后未存活" + fi + ;; + 2) + # 创建session2,在其中cd /root + tmux new-session -d -s "$SESSION2" 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TFAIL "创建tmux session $SESSION2 失败" + return + fi + + tmux send-keys -t "$SESSION2" "cd /root" C-m 2>/dev/null + sleep 1 + + # 验证两个session都存在 + local fail=0 + if ! tmux has-session -t "$SESSION1" 2>/dev/null; then + tst_res $TFAIL "session $SESSION1 不存在" + fail=1 + fi + if ! tmux has-session -t "$SESSION2" 2>/dev/null; then + tst_res $TFAIL "session $SESSION2 不存在" + fail=1 + fi + + if [ $fail -eq 0 ]; then + local count + count=$(tmux ls 2>/dev/null | grep -cE "(${SESSION1}|${SESSION2})") + if [ "$count" -eq 2 ]; then + tst_res $TPASS "两个session共存,tmux ls可正确列出" + else + tst_res $TFAIL "tmux ls列出session数量不正确: $count" + fi + fi + ;; + 3) + # 验证session中的工作目录保持,然后销毁 + local fail=0 + + # 检查session1目录为/home + if tmux has-session -t "$SESSION1" 2>/dev/null; then + local dir1 + tmux send-keys -t "$SESSION1" "pwd > /tmp/tmux_tc005_dir1_$$" C-m 2>/dev/null + sleep 1 + dir1=$(cat /tmp/tmux_tc005_dir1_$$ 2>/dev/null) + rm -f /tmp/tmux_tc005_dir1_$$ + + if [ "$dir1" = "/home" ]; then + tst_res $TINFO "session1目录保持为/home" + else + tst_res $TFAIL "session1目录不是/home,实际: $dir1" + fail=1 + fi + + # exit销毁session1 + tmux send-keys -t "$SESSION1" "exit" C-m 2>/dev/null + sleep 1 + else + tst_res $TFAIL "session1不存在,无法验证" + fail=1 + fi + + # 检查session2目录为/root + if tmux has-session -t "$SESSION2" 2>/dev/null; then + local dir2 + tmux send-keys -t "$SESSION2" "pwd > /tmp/tmux_tc005_dir2_$$" C-m 2>/dev/null + sleep 1 + dir2=$(cat /tmp/tmux_tc005_dir2_$$ 2>/dev/null) + rm -f /tmp/tmux_tc005_dir2_$$ + + if [ "$dir2" = "/root" ]; then + tst_res $TINFO "session2目录保持为/root" + else + tst_res $TFAIL "session2目录不是/root,实际: $dir2" + fail=1 + fi + + # exit销毁session2 + tmux send-keys -t "$SESSION2" "exit" C-m 2>/dev/null + sleep 1 + else + tst_res $TFAIL "session2不存在,无法验证" + fail=1 + fi + + # 验证session已销毁 + sleep 1 + if tmux has-session -t "$SESSION1" 2>/dev/null; then + tst_res $TFAIL "session1 exit后仍存在" + fail=1 + fi + if tmux has-session -t "$SESSION2" 2>/dev/null; then + tst_res $TFAIL "session2 exit后仍存在" + fail=1 + fi + + [ $fail -eq 0 ] && tst_res $TPASS "目录保持正确,exit后session已销毁" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/user/Makefile b/os_smoke_src/testcases/baseos/user/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/baseos/user/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/baseos/user/groupadd_tc001.sh b/os_smoke_src/testcases/baseos/user/groupadd_tc001.sh new file mode 100755 index 0000000..8d8e6a0 --- /dev/null +++ b/os_smoke_src/testcases/baseos/user/groupadd_tc001.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: groupadd_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证 groupadd 创建用户组功能是否正常 +# 前置条件: 需要 root 权限, 系统已安装 groupadd 命令 +# 用例步骤: +# 1. 使用 groupadd 创建测试用户组 +# 2. 使用 getent 验证用户组信息是否存在 +# 预期结果: +# 1. groupadd 执行成功, 返回值为 0 +# 2. getent 能查询到测试用户组信息 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "groupadd 创建测试用户组" + "getent 验证用户组存在" +) +tcnt=${#TCASE_DESC[@]} + +TEST_GROUP="ts_groupadd_tc001" + +setup() +{ + check_root || tst_brk $TCONF "需要 root 权限" + ensure_command "groupadd" "shadow-utils" + if getent group "${TEST_GROUP}" >/dev/null 2>&1; then + groupdel "${TEST_GROUP}" >/dev/null 2>&1 || true + fi +} + +cleanup() +{ + if getent group "${TEST_GROUP}" >/dev/null 2>&1; then + groupdel "${TEST_GROUP}" >/dev/null 2>&1 || true + fi +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "使用 groupadd 创建测试用户组 ${TEST_GROUP}" + if groupadd "${TEST_GROUP}" 2>/dev/null; then + tst_res $TPASS "groupadd 创建用户组成功" + else + tst_res $TFAIL "groupadd 创建用户组失败" + fi + ;; + 1) + tst_res $TINFO "使用 getent 验证用户组是否存在" + if getent group "${TEST_GROUP}" >/dev/null 2>&1; then + tst_res $TPASS "getent 查询到用户组 ${TEST_GROUP}" + else + tst_res $TFAIL "getent 未查询到用户组 ${TEST_GROUP}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/user/groupdel_tc001.sh b/os_smoke_src/testcases/baseos/user/groupdel_tc001.sh new file mode 100755 index 0000000..ed2b2da --- /dev/null +++ b/os_smoke_src/testcases/baseos/user/groupdel_tc001.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: groupdel_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证 groupdel 删除用户组功能是否正常 +# 前置条件: 需要 root 权限, 系统已安装 groupadd/groupdel 命令 +# 用例步骤: +# 1. 使用 groupdel 删除测试用户组 +# 2. 使用 getent 验证用户组已被删除 +# 预期结果: +# 1. groupdel 执行成功, 返回值为 0 +# 2. getent 查询不到该用户组 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "groupdel 删除测试用户组" + "确认用户组已被删除" +) +tcnt=${#TCASE_DESC[@]} + +TEST_GROUP="ts_groupdel_tc001" + +setup() +{ + check_root || tst_brk $TCONF "需要 root 权限" + ensure_command "groupdel" "shadow-utils" + ensure_command "groupadd" "shadow-utils" + # 确保测试用户组存在 + if ! getent group "${TEST_GROUP}" >/dev/null 2>&1; then + groupadd "${TEST_GROUP}" >/dev/null 2>&1 || tst_brk $TFAIL "无法创建测试用户组" + fi +} + +cleanup() +{ + if getent group "${TEST_GROUP}" >/dev/null 2>&1; then + groupdel "${TEST_GROUP}" >/dev/null 2>&1 || true + fi +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "使用 groupdel 删除测试用户组 ${TEST_GROUP}" + if groupdel "${TEST_GROUP}" 2>/dev/null; then + tst_res $TPASS "groupdel 删除用户组成功" + else + tst_res $TFAIL "groupdel 删除用户组失败" + fi + ;; + 1) + tst_res $TINFO "确认用户组已被删除" + if getent group "${TEST_GROUP}" >/dev/null 2>&1; then + tst_res $TFAIL "getent group 仍能查询到用户组 ${TEST_GROUP}" + else + tst_res $TPASS "用户组 ${TEST_GROUP} 已完全删除" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/user/id_tc001.sh b/os_smoke_src/testcases/baseos/user/id_tc001.sh new file mode 100755 index 0000000..b5d406d --- /dev/null +++ b/os_smoke_src/testcases/baseos/user/id_tc001.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: id_tc001 +# 用例类型: 功能测试 +# 优先级: P3 +# 用例描述: 验证 id 命令查询当前用户及指定用户 UID/GID/组信息功能是否正常 +# 前置条件: 系统已安装 id 命令 +# 用例步骤: +# 1. 执行 id 查询当前用户信息 +# 2. 执行 id -u/-g 查询当前用户的 UID/GID +# 3. 执行 id root 查询 root 用户信息 +# 预期结果: +# 1. id 执行成功, 输出包含 uid/gid 信息 +# 2. id -u/-g 返回值为数字 +# 3. id root 执行成功, 输出中包含 uid=0 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "id 查询当前用户信息" + "id -u/-g 查询 UID/GID" + "id root 查询 root 用户信息" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + ensure_command "id" "coreutils" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "执行 id 查询当前用户信息" + local output_self + output_self=$(id 2>/dev/null) + if [ -z "${output_self}" ]; then + tst_res $TFAIL "id 输出为空" + return + fi + local fail=0 + echo "${output_self}" | grep -q "uid=" || fail=1 + echo "${output_self}" | grep -q "gid=" || fail=1 + if [ $fail -eq 0 ]; then + tst_res $TPASS "id 输出包含 uid/gid 信息" + else + tst_res $TFAIL "id 输出缺少 uid 或 gid 信息" + fi + ;; + 1) + tst_res $TINFO "执行 id -u 和 id -g" + local uid_val gid_val fail=0 + uid_val=$(id -u 2>/dev/null) + gid_val=$(id -g 2>/dev/null) + echo "${uid_val}" | grep -Eq '^[0-9]+$' || fail=1 + echo "${gid_val}" | grep -Eq '^[0-9]+$' || fail=1 + if [ $fail -eq 0 ]; then + tst_res $TPASS "id -u=${uid_val}, id -g=${gid_val} 均为数字" + else + tst_res $TFAIL "id -u(${uid_val}) 或 id -g(${gid_val}) 不是数字" + fi + ;; + 2) + tst_res $TINFO "执行 id root" + local output_root + output_root=$(id root 2>/dev/null) + if [ -z "${output_root}" ]; then + tst_res $TFAIL "id root 输出为空" + elif echo "${output_root}" | grep -q "uid=0("; then + tst_res $TPASS "id root 输出包含 uid=0 信息" + else + tst_res $TFAIL "id root 输出中未包含 uid=0 信息" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/user/useradd_tc001.sh b/os_smoke_src/testcases/baseos/user/useradd_tc001.sh new file mode 100755 index 0000000..c5efc1c --- /dev/null +++ b/os_smoke_src/testcases/baseos/user/useradd_tc001.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: useradd_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证 useradd 创建用户及其 home 目录功能是否正常 +# 前置条件: 需要 root 权限, 系统已安装 useradd 命令 +# 用例步骤: +# 1. 使用 useradd -m 创建测试用户并指定登录 shell +# 2. 使用 id/getent 验证用户信息, 并检查 home 目录是否存在 +# 预期结果: +# 1. useradd 执行成功, 返回值为 0 +# 2. id/getent 能查询到测试用户, home 目录存在且可访问 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "useradd 创建测试用户" + "验证用户信息和 home 目录" +) +tcnt=${#TCASE_DESC[@]} + +TEST_USER="ts_useradd_tc001" + +setup() +{ + check_root || tst_brk $TCONF "需要 root 权限" + ensure_command "useradd" "shadow-utils" + # 清理可能残留的测试用户 + if id "${TEST_USER}" >/dev/null 2>&1; then + userdel -rf "${TEST_USER}" >/dev/null 2>&1 || true + fi + if getent group "${TEST_USER}" >/dev/null 2>&1; then + groupdel "${TEST_USER}" 2>/dev/null || true + fi +} + +cleanup() +{ + if id "${TEST_USER}" >/dev/null 2>&1; then + userdel -rf "${TEST_USER}" >/dev/null 2>&1 || true + fi + if getent group "${TEST_USER}" >/dev/null 2>&1; then + groupdel "${TEST_USER}" 2>/dev/null || true + fi +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "使用 useradd -m 创建测试用户 ${TEST_USER}" + if useradd -m -s /bin/bash "${TEST_USER}" 2>/dev/null; then + tst_res $TPASS "useradd 创建用户成功" + else + tst_res $TFAIL "useradd 创建用户失败" + fi + ;; + 1) + tst_res $TINFO "验证用户信息和 home 目录" + if ! id "${TEST_USER}" >/dev/null 2>&1; then + tst_res $TFAIL "id 未查询到用户 ${TEST_USER}" + return + fi + local user_line user_home + user_line=$(getent passwd "${TEST_USER}") + if [ -z "${user_line}" ]; then + tst_res $TFAIL "getent passwd 未查询到用户 ${TEST_USER}" + return + fi + user_home=$(echo "${user_line}" | awk -F: '{print $6}') + if [ -z "${user_home}" ] || [ ! -d "${user_home}" ]; then + tst_res $TFAIL "home 目录 ${user_home} 不存在" + else + tst_res $TPASS "用户信息和 home 目录验证通过" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/user/userdel_tc001.sh b/os_smoke_src/testcases/baseos/user/userdel_tc001.sh new file mode 100755 index 0000000..8343fbe --- /dev/null +++ b/os_smoke_src/testcases/baseos/user/userdel_tc001.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: userdel_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证 userdel 删除用户及其 home 目录功能是否正常 +# 前置条件: 需要 root 权限, 系统已安装 useradd/userdel 命令 +# 用例步骤: +# 1. 创建测试用户后使用 userdel -r 删除 +# 2. 使用 id/getent 验证用户已被删除, home 目录被清理 +# 预期结果: +# 1. userdel -r 执行成功, 返回值为 0 +# 2. id/getent 查询不到该用户, home 目录被删除 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "userdel -r 删除测试用户" + "确认用户和 home 目录已删除" +) +tcnt=${#TCASE_DESC[@]} + +TEST_USER="ts_userdel_tc001" +USER_HOME="" + +setup() +{ + check_root || tst_brk $TCONF "需要 root 权限" + ensure_command "userdel" "shadow-utils" + ensure_command "useradd" "shadow-utils" + # 确保测试用户存在(先清理残留组) + if ! id "${TEST_USER}" >/dev/null 2>&1; then + if getent group "${TEST_USER}" >/dev/null 2>&1; then + groupdel "${TEST_USER}" 2>/dev/null || true + fi + useradd -m -s /bin/bash "${TEST_USER}" 2>&1 || tst_brk $TFAIL "无法创建测试用户" + fi + USER_HOME=$(getent passwd "${TEST_USER}" | awk -F: '{print $6}') +} + +cleanup() +{ + if id "${TEST_USER}" >/dev/null 2>&1; then + userdel -rf "${TEST_USER}" >/dev/null 2>&1 || true + fi + if getent group "${TEST_USER}" >/dev/null 2>&1; then + groupdel "${TEST_USER}" 2>/dev/null || true + fi +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "使用 userdel -r 删除测试用户 ${TEST_USER}" + if userdel -r "${TEST_USER}" 2>/dev/null; then + tst_res $TPASS "userdel -r 执行成功" + else + tst_res $TFAIL "userdel -r 执行失败" + fi + ;; + 1) + tst_res $TINFO "确认用户已被删除" + if id "${TEST_USER}" >/dev/null 2>&1; then + tst_res $TFAIL "id 仍能查询到用户 ${TEST_USER}" + return + fi + if getent passwd "${TEST_USER}" >/dev/null 2>&1; then + tst_res $TFAIL "getent passwd 仍能查询到用户 ${TEST_USER}" + return + fi + if [ -n "${USER_HOME}" ] && [ -e "${USER_HOME}" ]; then + tst_res $TFAIL "home 目录 ${USER_HOME} 仍然存在" + return + fi + tst_res $TPASS "用户和 home 目录已完全删除" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/user/usermod_tc001.sh b/os_smoke_src/testcases/baseos/user/usermod_tc001.sh new file mode 100755 index 0000000..18f8c41 --- /dev/null +++ b/os_smoke_src/testcases/baseos/user/usermod_tc001.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: usermod_tc001 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: 验证 usermod 修改用户属性功能是否正常(家目录、主组、登录shell) +# 前置条件: 需要 root 权限, 系统已安装 useradd/usermod/userdel 命令 +# 用例步骤: +# 1. 使用 usermod -d 修改用户家目录, 验证修改生效 +# 2. 使用 usermod -g 修改用户主组, 验证修改生效 +# 3. 使用 usermod -s 修改用户登录 shell, 验证修改生效 +# 预期结果: +# 1. 家目录字段已更新为新路径 +# 2. 用户主组 GID 已更新 +# 3. 登录 shell 已更新 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "usermod -d 修改用户家目录" + "usermod -g 修改用户主组" + "usermod -s 修改用户登录 shell" +) +tcnt=${#TCASE_DESC[@]} + +TEST_USER="ts_usermod_tc001" + +setup() +{ + check_root || tst_brk $TCONF "需要 root 权限" + ensure_command "usermod" "shadow-utils" + # 清理并重建测试用户 + if id "${TEST_USER}" >/dev/null 2>&1; then + pkill -u "${TEST_USER}" 2>/dev/null || true + userdel -rf "${TEST_USER}" 2>/dev/null || true + fi + groupdel "${TEST_USER}" 2>/dev/null || true + rm -rf "/home/${TEST_USER}" 2>/dev/null + useradd -m -s /bin/bash "${TEST_USER}" 2>&1 || tst_brk $TFAIL "无法创建测试用户" +} + +cleanup() +{ + if id "${TEST_USER}" >/dev/null 2>&1; then + pkill -u "${TEST_USER}" 2>/dev/null || true + userdel -rf "${TEST_USER}" 2>/dev/null || true + fi + # 无条件兜底清理残留组和 home 目录 + groupdel "${TEST_USER}" 2>/dev/null || true + rm -rf "/home/${TEST_USER}" "/tmp/${TEST_USER}" 2>/dev/null + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "使用 usermod -d 修改家目录为 /tmp/${TEST_USER}" + usermod -d "/tmp/${TEST_USER}" "${TEST_USER}" 2>/dev/null + local new_home + new_home=$(getent passwd "${TEST_USER}" | awk -F: '{print $6}') + if [ "${new_home}" = "/tmp/${TEST_USER}" ]; then + tst_res $TPASS "usermod -d 修改家目录成功: ${new_home}" + else + tst_res $TFAIL "usermod -d 修改家目录失败, 期望 /tmp/${TEST_USER}, 实际 ${new_home}" + fi + ;; + 1) + tst_res $TINFO "使用 usermod -g 0 修改主组为 root(GID=0)" + usermod -g 0 "${TEST_USER}" 2>/dev/null + local new_gid + new_gid=$(id -g "${TEST_USER}" 2>/dev/null) + if [ "${new_gid}" = "0" ]; then + tst_res $TPASS "usermod -g 修改主组成功: GID=${new_gid}" + else + tst_res $TFAIL "usermod -g 修改主组失败, 期望 GID=0, 实际 GID=${new_gid}" + fi + ;; + 2) + tst_res $TINFO "使用 usermod -s /sbin/nologin 修改登录 shell" + usermod -s /sbin/nologin "${TEST_USER}" 2>/dev/null + local new_shell + new_shell=$(getent passwd "${TEST_USER}" | awk -F: '{print $7}') + if [ "${new_shell}" = "/sbin/nologin" ]; then + tst_res $TPASS "usermod -s 修改 shell 成功: ${new_shell}" + else + tst_res $TFAIL "usermod -s 修改 shell 失败, 期望 /sbin/nologin, 实际 ${new_shell}" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/user/who_tc001.sh b/os_smoke_src/testcases/baseos/user/who_tc001.sh new file mode 100755 index 0000000..46e7232 --- /dev/null +++ b/os_smoke_src/testcases/baseos/user/who_tc001.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: who_tc001 +# 用例类型: 功能测试 +# 优先级: P3 +# 用例描述: 验证 who 命令查询当前登录用户信息功能是否正常 +# 前置条件: 系统已安装 who 命令 +# 用例步骤: +# 1. 执行 who 命令 +# 2. 执行 who -a 命令 +# 预期结果: +# 1. who 执行成功, 返回值为 0 +# 2. who -a 执行成功, 返回值为 0 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "who 查询登录用户" + "who -a 查询所有用户" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + ensure_command "who" "coreutils" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "执行 who" + if who >/dev/null 2>&1; then + tst_res $TPASS "who 执行成功" + else + tst_res $TFAIL "who 执行失败" + fi + ;; + 1) + tst_res $TINFO "执行 who -a" + if who -a >/dev/null 2>&1; then + tst_res $TPASS "who -a 执行成功" + else + tst_res $TFAIL "who -a 执行失败" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/baseos/user/whoami_tc001.sh b/os_smoke_src/testcases/baseos/user/whoami_tc001.sh new file mode 100755 index 0000000..91a2552 --- /dev/null +++ b/os_smoke_src/testcases/baseos/user/whoami_tc001.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: whoami_tc001 +# 用例类型: 功能测试 +# 优先级: P3 +# 用例描述: 验证 whoami 命令返回当前用户名功能是否正常 +# 前置条件: 系统已安装 whoami 命令 +# 用例步骤: +# 1. 执行 whoami 命令 +# 2. 对比 whoami 与 id -un 的输出是否一致 +# 预期结果: +# 1. whoami 执行成功, 输出非空 +# 2. whoami 与 id -un 输出一致 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "whoami 返回当前用户名" + "whoami 与 id -un 输出一致" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + ensure_command "whoami" "coreutils" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "执行 whoami" + local cur_user + cur_user=$(whoami 2>/dev/null) + if [ -z "${cur_user}" ]; then + tst_res $TFAIL "whoami 输出为空" + else + tst_res $TPASS "whoami 输出: ${cur_user}" + fi + ;; + 1) + tst_res $TINFO "对比 whoami 与 id -un 输出" + local cur_user id_user + cur_user=$(whoami 2>/dev/null) + id_user=$(id -un 2>/dev/null) + if [ -n "${cur_user}" ] && [ "${cur_user}" = "${id_user}" ]; then + tst_res $TPASS "whoami(${cur_user}) 与 id -un(${id_user}) 一致" + else + tst_res $TFAIL "whoami(${cur_user}) 与 id -un(${id_user}) 不一致" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/Makefile b/os_smoke_src/testcases/develop/Makefile new file mode 100755 index 0000000..12e8c3b --- /dev/null +++ b/os_smoke_src/testcases/develop/Makefile @@ -0,0 +1,22 @@ +subdir := $(shell find . -maxdepth 1 -type d) +dirs := $(filter-out .,$(subdir)) +dirs := $(basename $(patsubst ./%,%,$(dirs))) + +.PHONY: $(dirs) + +$(dirs): + echo $(dirs) + @for dir in $(dirs); do \ + $(MAKE) -C $$dir || exit "$$?"; \ + done + +install: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir install || exit "$$?"; \ + done + +clean: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir clean || exit "$$?"; \ + done + diff --git a/os_smoke_src/testcases/develop/buildtool/Makefile b/os_smoke_src/testcases/develop/buildtool/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/develop/buildtool/autoconf_tc001.sh b/os_smoke_src/testcases/develop/buildtool/autoconf_tc001.sh new file mode 100755 index 0000000..b53c369 --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/autoconf_tc001.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: autoconf_tc001 +# 描述: 验证 GNU Autotools (autoconf/automake) 构建系统基本功能 +# 等级: P1 +# 用例类型: 功能测试 +# 前置条件: autoconf/automake 可安装 +# 步骤: +# 1. 检查 autoconf/automake 版本 +# 2. 创建简单 autotools 项目 +# 3. 执行 autoreconf 生成 configure +# 4. 执行 configure && make && 验证产物 +# 预期结果: +# 1. 版本正常 +# 2. 项目结构正确 +# 3. configure 脚本生成成功 +# 4. 编译成功且输出正确 +#**************************************************# + + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 GNU Autotools 构建系统基本功能" +tcnt=4 + +WORK_DIR="" + +setup() +{ + ensure_command "gcc" + + ensure_command "autoconf" "autoconf automake" + + WORK_DIR=$(mktemp -d) + + # 创建简单 autotools 项目 + cat > "$WORK_DIR/hello.c" << 'CEOF' +#include +int main() { printf("hello autotools\n"); return 0; } +CEOF + + cat > "$WORK_DIR/configure.ac" << 'ACEOF' +AC_INIT([hello], [1.0]) +AM_INIT_AUTOMAKE([foreign -Wall -Werror]) +AC_PROG_CC +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT +ACEOF + + cat > "$WORK_DIR/Makefile.am" << 'AMEOF' +bin_PROGRAMS = hello +hello_SOURCES = hello.c +AMEOF +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 autoconf/automake 版本" + local acver amver + acver=$(autoconf --version 2>&1 | head -1) + amver=$(automake --version 2>&1 | head -1) + if [ -n "$acver" ] && [ -n "$amver" ]; then + tst_res $TPASS "autoconf: $acver, automake: $amver" + else + tst_res $TFAIL "版本获取失败" + fi + ;; + 1) + tst_res $TINFO "检查项目文件" + if [ -f "$WORK_DIR/configure.ac" ] && [ -f "$WORK_DIR/Makefile.am" ] && [ -f "$WORK_DIR/hello.c" ]; then + tst_res $TPASS "autotools 项目文件就绪" + else + tst_res $TFAIL "项目文件缺失" + fi + ;; + 2) + tst_res $TINFO "autoreconf 生成 configure" + cd "$WORK_DIR" || return + autoreconf --install 2>&1 + if [ $? -eq 0 ] && [ -x "$WORK_DIR/configure" ]; then + tst_res $TPASS "autoreconf 成功,configure 已生成" + else + tst_res $TFAIL "autoreconf 失败" + fi + ;; + 3) + tst_res $TINFO "configure && make && 验证" + cd "$WORK_DIR" || return + ./configure >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "configure 失败" + return + fi + make >/dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "make 失败" + return + fi + local output + output=$("$WORK_DIR/hello" 2>&1) + if echo "$output" | grep -q "hello autotools"; then + tst_res $TPASS "编译执行成功: $output" + else + tst_res $TFAIL "输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/buildtool/binutils_tc001.sh b/os_smoke_src/testcases/develop/buildtool/binutils_tc001.sh new file mode 100755 index 0000000..4adf84b --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/binutils_tc001.sh @@ -0,0 +1,173 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: binutils_tc001 +# 描述: 验证 binutils 工具集核心功能(nm/objdump/readelf/size/strings/strip/objcopy/addr2line) +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: binutils 和 gcc 已安装 +# 步骤: +# 1. nm 查看符号表 +# 2. objdump 反汇编 +# 3. readelf 查看 ELF 头 +# 4. size 查看段大小 +# 5. strings 查看可打印字符串 +# 6. strip 剥离符号 +# 7. objcopy 复制二进制 +# 8. addr2line 地址转行号 +# 9. 执行原始二进制验证功能 +# 预期结果: +# 1. nm 输出包含 main 符号 +# 2. objdump 反汇编包含 main 函数 +# 3. readelf 正常识别 ELF 格式 +# 4. size 输出包含 text 段 +# 5. strings 输出包含预期字符串 +# 6. strip 后文件大小减小 +# 7. objcopy 复制成功 +# 8. addr2line 执行成功 +# 9. 执行输出正确 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 binutils 工具集核心功能" +tcnt=9 + +WORK_DIR="" + +setup() +{ + ensure_command "gcc" + for tool in nm objdump readelf size strings strip objcopy addr2line; do + ensure_command "$tool" "binutils" + done + WORK_DIR=$(mktemp -d) + cp "$(dirname "$0")/binutils_test/binutils_test.c" "$WORK_DIR/" + gcc -g "$WORK_DIR/binutils_test.c" -o "$WORK_DIR/binutils_test" 2>&1 + if [ $? -ne 0 ]; then + tst_brk $TFAIL "编译 binutils_test.c 失败" + fi + cp "$WORK_DIR/binutils_test" "$WORK_DIR/binutils_test_stripped" +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + local bin="$WORK_DIR/binutils_test" + case $1 in + 0) + tst_res $TINFO "nm 查看符号表" + local output + output=$(nm "$bin" 2>&1) + if echo "$output" | grep -q "main"; then + tst_res $TPASS "nm 输出包含 main 符号" + else + tst_res $TFAIL "nm 输出未找到 main 符号" + fi + ;; + 1) + tst_res $TINFO "objdump 反汇编" + local output + output=$(objdump -d "$bin" 2>&1) + if echo "$output" | grep -q "
"; then + tst_res $TPASS "objdump 反汇编包含 main 函数" + else + tst_res $TFAIL "objdump 反汇编未找到 main 函数" + fi + ;; + 2) + tst_res $TINFO "readelf 查看 ELF 头" + local output + output=$(readelf -h "$bin" 2>&1) + if echo "$output" | grep -q "ELF"; then + tst_res $TPASS "readelf 正常识别 ELF 格式" + else + tst_res $TFAIL "readelf 未识别 ELF 格式" + fi + ;; + 3) + tst_res $TINFO "size 查看段大小" + local output + output=$(size "$bin" 2>&1) + if echo "$output" | grep -q "text"; then + tst_res $TPASS "size 输出包含 text 段" + else + tst_res $TFAIL "size 输出异常" + fi + ;; + 4) + tst_res $TINFO "strings 查看可打印字符串" + local output + output=$(strings "$bin" 2>&1) + if echo "$output" | grep -q "glob"; then + tst_res $TPASS "strings 输出包含预期字符串" + else + tst_res $TFAIL "strings 输出未找到预期字符串" + fi + ;; + 5) + tst_res $TINFO "strip 剥离符号" + local stripped="$WORK_DIR/binutils_test_stripped" + local before after + before=$(wc -c < "$stripped") + strip "$stripped" 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "strip 执行失败" + return + fi + after=$(wc -c < "$stripped") + if [ "$after" -lt "$before" ]; then + tst_res $TPASS "strip 成功,文件从 ${before}B 减至 ${after}B" + else + tst_res $TFAIL "strip 后文件未缩小" + fi + ;; + 6) + tst_res $TINFO "objcopy 复制二进制" + local copy="$WORK_DIR/binutils_test_copy" + objcopy "$bin" "$copy" 2>&1 + if [ $? -eq 0 ] && [ -f "$copy" ]; then + tst_res $TPASS "objcopy 复制成功" + else + tst_res $TFAIL "objcopy 复制失败" + fi + ;; + 7) + tst_res $TINFO "addr2line 地址转行号" + local addr + addr=$(nm "$bin" 2>/dev/null | grep " T main" | awk '{print $1}') + if [ -z "$addr" ]; then + tst_res $TWARN "无法获取 main 地址,跳过 addr2line 测试" + return + fi + local output + output=$(addr2line -e "$bin" "$addr" 2>&1) + if [ -n "$output" ] && ! echo "$output" | grep -q "??"; then + tst_res $TPASS "addr2line 输出: $output" + else + tst_res $TPASS "addr2line 执行成功(调试信息可能受优化影响): $output" + fi + ;; + 8) + tst_res $TINFO "执行原始二进制验证功能" + local output + output=$("$bin" 2>&1) + if echo "$output" | grep -q "glob=97"; then + tst_res $TPASS "执行输出正确: $output" + else + tst_res $TFAIL "执行输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/buildtool/binutils_test/Makefile b/os_smoke_src/testcases/develop/buildtool/binutils_test/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/binutils_test/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/develop/buildtool/binutils_test/binutils_test.c b/os_smoke_src/testcases/develop/buildtool/binutils_test/binutils_test.c new file mode 100755 index 0000000..569392f --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/binutils_test/binutils_test.c @@ -0,0 +1,36 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +#include +int glob = 45; + +int verylongfun() +{ + glob += 2; + glob *= 2; +} + +void mailand() +{ + glob = 46; +} + +int foo(int x) { + return x + 92; +} + +void goodbye() { + ++glob; +} + +int main() { + mailand(); + foo(glob); + verylongfun(); + goodbye(); + printf("glob=%d\n",glob); + return 0; +} diff --git a/os_smoke_src/testcases/develop/buildtool/cmake_tc001.sh b/os_smoke_src/testcases/develop/buildtool/cmake_tc001.sh new file mode 100755 index 0000000..fe40914 --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/cmake_tc001.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cmake_tc001 +# 描述: 验证 CMake 构建系统安装及项目构建功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: cmake 已安装 +# 步骤: +# 1. 检查 cmake 版本 +# 2. cmake 生成构建文件 +# 3. make 编译 +# 4. 执行并验证输出 +# 预期结果: +# 1. cmake 版本正常 +# 2. 构建文件生成成功 +# 3. 编译成功 +# 4. 输出 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 CMake 构建系统安装及项目构建功能" +tcnt=4 + +WORK_DIR="" + +setup() +{ + ensure_command "cmake" + ensure_command "gcc" + WORK_DIR=$(mktemp -d) + cp "$(dirname "$0")/cmake_test/cmake_test.c" "$WORK_DIR/" + cp "$(dirname "$0")/cmake_test/strutil.c" "$WORK_DIR/" + cp "$(dirname "$0")/cmake_test/CMakeLists.txt" "$WORK_DIR/" +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 cmake 版本" + local ver + ver=$(cmake --version 2>&1 | head -1) + if echo "$ver" | grep -qi "cmake"; then + tst_res $TPASS "cmake 版本: $ver" + else + tst_res $TFAIL "无法获取 cmake 版本" + fi + ;; + 1) + tst_res $TINFO "cmake 生成构建文件" + mkdir -p "$WORK_DIR/build" + (cd "$WORK_DIR/build" && cmake "$WORK_DIR") 2>&1 + if [ $? -eq 0 ] && [ -f "$WORK_DIR/build/Makefile" ]; then + tst_res $TPASS "cmake 生成构建文件成功" + else + tst_res $TFAIL "cmake 生成构建文件失败" + fi + ;; + 2) + tst_res $TINFO "make 编译" + make -C "$WORK_DIR/build" 2>&1 + if [ $? -eq 0 ]; then + tst_res $TPASS "编译成功" + else + tst_res $TFAIL "编译失败" + fi + ;; + 3) + tst_res $TINFO "执行编译产物" + local output + output=$("$WORK_DIR/build/cmake_test" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "输出正确: $output" + else + tst_res $TFAIL "输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/buildtool/cmake_test/CMakeLists.txt b/os_smoke_src/testcases/develop/buildtool/cmake_test/CMakeLists.txt new file mode 100755 index 0000000..c6a0067 --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/cmake_test/CMakeLists.txt @@ -0,0 +1,3 @@ +project(cmake_test C) +add_executable(cmake_test cmake_test.c strutil.c) +target_link_libraries(cmake_test m) diff --git a/os_smoke_src/testcases/develop/buildtool/cmake_test/Makefile b/os_smoke_src/testcases/develop/buildtool/cmake_test/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/cmake_test/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/develop/buildtool/cmake_test/cmake_test.c b/os_smoke_src/testcases/develop/buildtool/cmake_test/cmake_test.c new file mode 100755 index 0000000..4bedaa2 --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/cmake_test/cmake_test.c @@ -0,0 +1,47 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +#include +#include +#include +#include + +extern int strutil_reverse(char *dst, const char *src, int maxlen); +extern int strutil_count_char(const char *s, char c); + +int main(void) +{ + int errors = 0; + + /* Test multi-file linking via cmake */ + char buf[32]; + int rc = strutil_reverse(buf, "dlrow olleh", sizeof(buf)); + if (rc != 0 || strcmp(buf, "hello world") != 0) { + fprintf(stderr, "FAIL: strutil_reverse\n"); + errors++; + } + + int cnt = strutil_count_char("hello world", 'l'); + if (cnt != 3) { + fprintf(stderr, "FAIL: strutil_count_char got %d want 3\n", cnt); + errors++; + } + + /* Test math linkage */ + double val = sqrt(144.0); + if (fabs(val - 12.0) > 0.001) { + fprintf(stderr, "FAIL: sqrt(144)=%f\n", val); + errors++; + } + + if (errors == 0) { + printf("hello world\n"); + } else { + fprintf(stderr, "%d cmake tests failed\n", errors); + return 1; + } + return 0; +} diff --git a/os_smoke_src/testcases/develop/buildtool/cmake_test/strutil.c b/os_smoke_src/testcases/develop/buildtool/cmake_test/strutil.c new file mode 100644 index 0000000..141696c --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/cmake_test/strutil.c @@ -0,0 +1,30 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +#include + +int strutil_reverse(char *dst, const char *src, int maxlen) +{ + int len = (int)strlen(src); + if (len >= maxlen) return -1; + + int i; + for (i = 0; i < len; i++) { + dst[i] = src[len - 1 - i]; + } + dst[len] = '\0'; + return 0; +} + +int strutil_count_char(const char *s, char c) +{ + int count = 0; + while (*s) { + if (*s == c) count++; + s++; + } + return count; +} diff --git a/os_smoke_src/testcases/develop/buildtool/git_tc001.sh b/os_smoke_src/testcases/develop/buildtool/git_tc001.sh new file mode 100755 index 0000000..1088411 --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/git_tc001.sh @@ -0,0 +1,109 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: git_tc001 +# 描述: 验证 Git 版本管理工具核心功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: git 已安装 +# 步骤: +# 1. 检查 git 版本 +# 2. git init 初始化仓库 +# 3. git add && git commit 提交文件 +# 4. git branch 和 git checkout 分支操作 +# 5. git log 查看提交历史 +# 预期结果: +# 1. git 版本正常 +# 2. 仓库初始化成功 +# 3. 提交成功 +# 4. 分支创建和切换成功 +# 5. 日志包含提交信息 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 Git 版本管理工具核心功能" +tcnt=5 + +WORK_DIR="" + +setup() +{ + ensure_command "git" + WORK_DIR=$(mktemp -d) +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 git 版本" + local ver + ver=$(git --version 2>&1) + if echo "$ver" | grep -q "git version"; then + tst_res $TPASS "git 版本: $ver" + else + tst_res $TFAIL "无法获取 git 版本" + fi + ;; + 1) + tst_res $TINFO "git init 初始化仓库" + git init "$WORK_DIR/testrepo" >/dev/null 2>&1 + if [ -d "$WORK_DIR/testrepo/.git" ]; then + tst_res $TPASS "git init 成功" + else + tst_res $TFAIL "git init 失败" + fi + ;; + 2) + tst_res $TINFO "git add && git commit" + cd "$WORK_DIR/testrepo" || return + git config user.email "test@test.com" 2>/dev/null + git config user.name "Test" 2>/dev/null + echo "hello git" > README + git add README 2>&1 + git commit -m "initial commit" >/dev/null 2>&1 + if [ $? -eq 0 ]; then + tst_res $TPASS "git commit 成功" + else + tst_res $TFAIL "git commit 失败" + fi + ;; + 3) + tst_res $TINFO "git branch 和 checkout" + cd "$WORK_DIR/testrepo" || return + git branch test-branch 2>&1 + git checkout test-branch >/dev/null 2>&1 + local current + current=$(git rev-parse --abbrev-ref HEAD 2>&1) + if [ "$current" = "test-branch" ]; then + tst_res $TPASS "git branch/checkout 成功,当前分支: $current" + else + tst_res $TFAIL "分支切换失败,当前分支: $current" + fi + ;; + 4) + tst_res $TINFO "git log 查看历史" + cd "$WORK_DIR/testrepo" || return + local log + log=$(git log --oneline 2>&1) + if echo "$log" | grep -q "initial commit"; then + tst_res $TPASS "git log 包含提交记录" + else + tst_res $TFAIL "git log 未找到提交记录" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/buildtool/make_tc001.sh b/os_smoke_src/testcases/develop/buildtool/make_tc001.sh new file mode 100755 index 0000000..16efa15 --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/make_tc001.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: make_tc001 +# 描述: 验证 GNU Make 构建工具安装及编译执行功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: make 已安装 +# 步骤: +# 1. 检查 make 版本(应为 GNU Make) +# 2. 使用 make 编译测试项目 +# 3. 执行编译产物并验证输出 +# 4. make clean 清理 +# 预期结果: +# 1. make 版本包含 GNU Make +# 2. 编译成功 +# 3. 输出 hello world +# 4. 清理成功 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 GNU Make 构建工具安装及编译执行功能" +tcnt=4 + +WORK_DIR="" + +setup() +{ + ensure_command "make" + ensure_command "gcc" + WORK_DIR=$(mktemp -d) + cp -r "$(dirname "$0")/make_test/"* "$WORK_DIR/" +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 make 版本" + local ver + ver=$(make --version 2>&1 | head -1) + if echo "$ver" | grep -qi "GNU Make"; then + tst_res $TPASS "make 版本: $ver" + else + tst_res $TFAIL "make 不是 GNU Make: $ver" + fi + ;; + 1) + tst_res $TINFO "使用 make 编译测试项目" + make -C "$WORK_DIR" 2>&1 + if [ $? -eq 0 ]; then + tst_res $TPASS "make 编译成功" + else + tst_res $TFAIL "make 编译失败" + fi + ;; + 2) + tst_res $TINFO "执行编译产物" + local output bin_name + bin_name=$(find "$WORK_DIR" -maxdepth 1 -type f -executable ! -name "*.c" ! -name "Makefile" | head -1) + if [ -z "$bin_name" ]; then + tst_res $TFAIL "未找到编译产物" + return + fi + output=$("$bin_name" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "输出正确: $output" + else + tst_res $TFAIL "输出异常: $output" + fi + ;; + 3) + tst_res $TINFO "make clean 清理" + make -C "$WORK_DIR" clean 2>&1 + if [ $? -eq 0 ]; then + tst_res $TPASS "make clean 成功" + else + tst_res $TFAIL "make clean 失败" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/buildtool/make_test/Makefile b/os_smoke_src/testcases/develop/buildtool/make_test/Makefile new file mode 100755 index 0000000..93586aa --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/make_test/Makefile @@ -0,0 +1,34 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := make_test +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +ifneq ($(CASES_SOURCE),) +$(CASES_BIN): $(CASES_OBJ) + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/develop/buildtool/make_test/make_test.c b/os_smoke_src/testcases/develop/buildtool/make_test/make_test.c new file mode 100755 index 0000000..7208e22 --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/make_test/make_test.c @@ -0,0 +1,43 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +#include +#include +#include + +extern int mathlib_add(int a, int b); +extern int mathlib_mul(int a, int b); + +int main(void) +{ + int errors = 0; + + /* Test multi-file linking */ + if (mathlib_add(2, 3) != 5) { + fprintf(stderr, "FAIL: mathlib_add\n"); + errors++; + } + if (mathlib_mul(3, 4) != 12) { + fprintf(stderr, "FAIL: mathlib_mul\n"); + errors++; + } + + /* Test basic string ops to verify libc linkage */ + char buf[32]; + snprintf(buf, sizeof(buf), "%s %s", "hello", "world"); + if (strcmp(buf, "hello world") != 0) { + fprintf(stderr, "FAIL: snprintf\n"); + errors++; + } + + if (errors == 0) { + printf("hello world\n"); + } else { + fprintf(stderr, "%d make tests failed\n", errors); + return 1; + } + return 0; +} diff --git a/os_smoke_src/testcases/develop/buildtool/make_test/mathlib.c b/os_smoke_src/testcases/develop/buildtool/make_test/mathlib.c new file mode 100644 index 0000000..bf457a5 --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/make_test/mathlib.c @@ -0,0 +1,16 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ + +int mathlib_add(int a, int b) +{ + return a + b; +} + +int mathlib_mul(int a, int b) +{ + return a * b; +} diff --git a/os_smoke_src/testcases/develop/buildtool/rpmbuild_tc001.sh b/os_smoke_src/testcases/develop/buildtool/rpmbuild_tc001.sh new file mode 100755 index 0000000..94cc69e --- /dev/null +++ b/os_smoke_src/testcases/develop/buildtool/rpmbuild_tc001.sh @@ -0,0 +1,129 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: rpmbuild_tc001 +# 描述: 验证 RPM 包构建工具链基本功能 +# 等级: P1 +# 用例类型: 功能测试 +# 前置条件: rpm-build 可安装 +# 步骤: +# 1. 检查 rpmbuild 版本 +# 2. 检查构建目录和文件是否就绪 +# 3. rpmbuild 构建 RPM 包 +# 4. 验证 RPM 包信息可查询 +# 预期结果: +# 1. rpmbuild 版本正常 +# 2. spec 文件和源码 tarball 就绪 +# 3. RPM 包构建成功 +# 4. rpm -qip 可查询包信息 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 RPM 包构建工具链基本功能" +tcnt=4 + +WORK_DIR="" + +setup() +{ + ensure_command "rpmbuild" "rpm-build" + + WORK_DIR=$(mktemp -d) + mkdir -p "$WORK_DIR"/{BUILD,RPMS,SOURCES,SPECS,SRPMS} + + # 创建源码 tarball + mkdir -p "$WORK_DIR/hello-1.0" + cat > "$WORK_DIR/hello-1.0/hello.sh" << 'SHEOF' +#!/bin/bash +echo "hello rpmbuild" +SHEOF + chmod +x "$WORK_DIR/hello-1.0/hello.sh" + tar czf "$WORK_DIR/SOURCES/hello-1.0.tar.gz" -C "$WORK_DIR" hello-1.0 + + # 创建 spec 文件 + cat > "$WORK_DIR/SPECS/hello.spec" << 'SPECEOF' +Name: hello-test +Version: 1.0 +Release: 1%{?dist} +Summary: Test RPM package +License: GPL +Source0: hello-1.0.tar.gz + +%description +A simple test RPM package for rpmbuild validation. + +%prep +%setup -q -n hello-1.0 + +%install +mkdir -p %{buildroot}/usr/local/bin +cp hello.sh %{buildroot}/usr/local/bin/hello-test + +%files +/usr/local/bin/hello-test +SPECEOF +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 rpmbuild 版本" + local ver + ver=$(rpmbuild --version 2>&1) + if echo "$ver" | grep -qi "RPM"; then + tst_res $TPASS "rpmbuild 版本: $ver" + else + tst_res $TFAIL "无法获取 rpmbuild 版本" + fi + ;; + 1) + tst_res $TINFO "检查构建目录和文件" + if [ -f "$WORK_DIR/SPECS/hello.spec" ] && [ -f "$WORK_DIR/SOURCES/hello-1.0.tar.gz" ]; then + tst_res $TPASS "构建目录和文件就绪" + else + tst_res $TFAIL "构建文件缺失" + fi + ;; + 2) + tst_res $TINFO "rpmbuild 构建 RPM 包" + rpmbuild --define "_topdir $WORK_DIR" -bb "$WORK_DIR/SPECS/hello.spec" 2>&1 + local rpm_file + rpm_file=$(find "$WORK_DIR/RPMS" -name "*.rpm" | head -1) + if [ -n "$rpm_file" ] && [ -f "$rpm_file" ]; then + tst_res $TPASS "RPM 包构建成功: $(basename "$rpm_file")" + else + tst_res $TFAIL "RPM 包构建失败,未生成 .rpm 文件" + fi + ;; + 3) + tst_res $TINFO "验证 RPM 包信息" + local rpm_file + rpm_file=$(find "$WORK_DIR/RPMS" -name "*.rpm" | head -1) + if [ -z "$rpm_file" ]; then + tst_res $TFAIL "未找到 RPM 包" + return + fi + local info + info=$(rpm -qip "$rpm_file" 2>&1) + if echo "$info" | grep -q "hello-test"; then + tst_res $TPASS "RPM 包信息正确" + else + tst_res $TFAIL "RPM 包信息异常: $info" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/compiler/Makefile b/os_smoke_src/testcases/develop/compiler/Makefile new file mode 100755 index 0000000..d61844a --- /dev/null +++ b/os_smoke_src/testcases/develop/compiler/Makefile @@ -0,0 +1,37 @@ +EXCLUDE_SOURCES := clang_test.c gcc_test.c gxx_test.cpp +CASES_SOURCE := $(filter-out $(EXCLUDE_SOURCES), $(wildcard *.c)) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += -lm +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/develop/compiler/clang_tc001.sh b/os_smoke_src/testcases/develop/compiler/clang_tc001.sh new file mode 100755 index 0000000..d6a7175 --- /dev/null +++ b/os_smoke_src/testcases/develop/compiler/clang_tc001.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: clang_tc001 +# 描述: 验证 Clang 编译器安装及基本编译执行功能 +# 等级: P1 +# 用例类型: 功能测试 +# 前置条件: clang 可通过包管理器安装 +# 步骤: +# 1. 检查 clang 版本 +# 2. 编译测试程序 +# 3. 执行并验证输出 +# 预期结果: +# 1. 版本正常 +# 2. 编译成功 +# 3. 输出 test clang +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 Clang 编译器安装及基本编译执行功能" +tcnt=3 + +WORK_DIR="" +GCC_TOOLCHAIN_FLAG="" + +setup() +{ + WORK_DIR=$(mktemp -d) + cp "$(dirname "$0")/clang_test.c" "$WORK_DIR/" + + ensure_command "clang" + + # 动态获取 gcc toolchain 路径,传给 clang 避免找不到 crtbegin.o/libgcc + local gcc_libdir + gcc_libdir=$(dirname "$(gcc -print-libgcc-file-name 2>/dev/null)" 2>/dev/null) + if [ -n "$gcc_libdir" ] && [ -d "$gcc_libdir" ]; then + tst_res $TINFO "gcc toolchain 路径: $gcc_libdir" + GCC_TOOLCHAIN_FLAG="-B${gcc_libdir} -L${gcc_libdir}" + else + tst_res $TINFO "未找到 gcc toolchain 路径,使用默认" + fi +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 clang 版本" + local ver + ver=$(clang --version 2>&1 | head -1) + if [ -n "$ver" ]; then + tst_res $TPASS "clang 版本: $ver" + else + tst_res $TFAIL "无法获取 clang 版本" + fi + ;; + 1) + tst_res $TINFO "编译 clang_test.c" + clang -std=c99 $GCC_TOOLCHAIN_FLAG "$WORK_DIR/clang_test.c" -o "$WORK_DIR/clang_test" 2>&1 + if [ $? -eq 0 ] && [ -x "$WORK_DIR/clang_test" ]; then + tst_res $TPASS "clang 编译成功" + else + tst_res $TFAIL "clang 编译失败" + fi + ;; + 2) + tst_res $TINFO "执行编译产物并验证输出" + local output + output=$("$WORK_DIR/clang_test" 2>&1) + if echo "$output" | grep -q "test clang"; then + tst_res $TPASS "输出正确: $output" + else + tst_res $TFAIL "输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/compiler/clang_test.c b/os_smoke_src/testcases/develop/compiler/clang_test.c new file mode 100755 index 0000000..208bdfc --- /dev/null +++ b/os_smoke_src/testcases/develop/compiler/clang_test.c @@ -0,0 +1,131 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +#include +#include +#include +#include +#include + +static int errors = 0; + +#define CHECK(cond, msg) do { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL: %s\n", msg); \ + errors++; \ + } \ +} while(0) + +/* Test 1: C99/C11 features */ +void test_c99_features(void) +{ + /* Variable-length concept with designated initializers */ + struct { + int x, y, z; + } pt = { .x = 1, .z = 3, .y = 2 }; + CHECK(pt.x == 1 && pt.y == 2 && pt.z == 3, "designated initializer"); + + /* Compound literals */ + int *arr = (int[]){10, 20, 30}; + CHECK(arr[0] == 10 && arr[2] == 30, "compound literal"); + + /* _Bool type */ + _Bool flag = 1; + CHECK(flag == true, "_Bool type"); + + /* Flexible array-like: VLA */ + int n = 5; + int vla[n]; + for (int i = 0; i < n; i++) vla[i] = i * i; + CHECK(vla[4] == 16, "VLA"); +} + +/* Test 2: typeof and __builtin functions (GCC/Clang extensions) */ +void test_builtins(void) +{ + int val = 0x00FF0000; + int leading = __builtin_clz((unsigned)val); + CHECK(leading >= 0, "__builtin_clz"); + + int popcount = __builtin_popcount(0xFF); + CHECK(popcount == 8, "__builtin_popcount"); + + CHECK(__builtin_expect(1, 1) == 1, "__builtin_expect"); +} + +/* Test 3: Enum and switch */ +typedef enum { + COLOR_RED = 0, + COLOR_GREEN, + COLOR_BLUE, + COLOR_COUNT +} Color; + +const char *color_name(Color c) +{ + switch (c) { + case COLOR_RED: return "red"; + case COLOR_GREEN: return "green"; + case COLOR_BLUE: return "blue"; + default: return "unknown"; + } +} + +void test_enum(void) +{ + CHECK(COLOR_COUNT == 3, "enum count"); + CHECK(strcmp(color_name(COLOR_GREEN), "green") == 0, "enum switch"); +} + +/* Test 4: Recursive function and macro */ +int factorial(int n) +{ + if (n <= 1) return 1; + return n * factorial(n - 1); +} + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +void test_recursion_macro(void) +{ + CHECK(factorial(5) == 120, "factorial"); + CHECK(factorial(0) == 1, "factorial(0)"); + CHECK(MAX(3, 7) == 7, "MAX macro"); + CHECK(MIN(3, 7) == 3, "MIN macro"); +} + +/* Test 5: Alignment and sizeof */ +void test_alignment(void) +{ + CHECK(sizeof(char) == 1, "sizeof char"); + CHECK(sizeof(int) >= 4, "sizeof int"); + CHECK(sizeof(void *) >= 4, "sizeof pointer"); + CHECK(sizeof(int64_t) == 8, "sizeof int64_t"); + + struct __attribute__((packed)) Packed { + char a; + int b; + }; + CHECK(sizeof(struct Packed) == sizeof(char) + sizeof(int), "packed struct"); +} + +int main(void) +{ + test_c99_features(); + test_builtins(); + test_enum(); + test_recursion_macro(); + test_alignment(); + + if (errors == 0) { + printf("test clang\n"); + } else { + fprintf(stderr, "%d clang tests failed\n", errors); + return 1; + } + return 0; +} diff --git a/os_smoke_src/testcases/develop/compiler/gcc_tc001.sh b/os_smoke_src/testcases/develop/compiler/gcc_tc001.sh new file mode 100755 index 0000000..438c97c --- /dev/null +++ b/os_smoke_src/testcases/develop/compiler/gcc_tc001.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: gcc_tc001 +# 描述: 验证 GCC C 编译器安装及基本编译执行功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: gcc 已安装 +# 步骤: +# 1. 检查 gcc 版本信息 +# 2. 用 gcc 编译测试程序 gcc_test.c +# 3. 执行编译产物并验证输出 +# 预期结果: +# 1. 版本信息正常输出 +# 2. 编译成功 +# 3. 输出包含 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 GCC C 编译器安装及基本编译执行功能" +tcnt=3 + +WORK_DIR="" + +setup() +{ + ensure_command "gcc" + WORK_DIR=$(mktemp -d) + cp "$(dirname "$0")/gcc_test.c" "$WORK_DIR/" +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 gcc 版本" + local ver + ver=$(gcc --version 2>&1 | head -1) + if [ -n "$ver" ]; then + tst_res $TPASS "gcc 版本: $ver" + else + tst_res $TFAIL "无法获取 gcc 版本" + fi + ;; + 1) + tst_res $TINFO "编译 gcc_test.c" + gcc "$WORK_DIR/gcc_test.c" -o "$WORK_DIR/gcc_test" -lm 2>&1 + if [ $? -eq 0 ] && [ -x "$WORK_DIR/gcc_test" ]; then + tst_res $TPASS "gcc 编译成功" + else + tst_res $TFAIL "gcc 编译失败" + fi + ;; + 2) + tst_res $TINFO "执行编译产物并验证输出" + local output + output=$("$WORK_DIR/gcc_test" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "输出正确: $output" + else + tst_res $TFAIL "输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/compiler/gcc_test.c b/os_smoke_src/testcases/develop/compiler/gcc_test.c new file mode 100755 index 0000000..564922e --- /dev/null +++ b/os_smoke_src/testcases/develop/compiler/gcc_test.c @@ -0,0 +1,120 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +#include +#include +#include +#include +#include + +static int errors = 0; + +#define CHECK(cond, msg) do { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL: %s\n", msg); \ + errors++; \ + } \ +} while(0) + +/* Test 1: Pointer and memory operations */ +void test_pointer_memory(void) +{ + int arr[] = {5, 3, 1, 4, 2}; + int *p = arr; + CHECK(*p == 5, "pointer deref"); + CHECK(*(p + 2) == 1, "pointer arithmetic"); + + char *buf = (char *)malloc(64); + CHECK(buf != NULL, "malloc"); + strcpy(buf, "hello world"); + CHECK(strcmp(buf, "hello world") == 0, "strcpy/strcmp"); + free(buf); + + int *block = (int *)calloc(10, sizeof(int)); + CHECK(block != NULL, "calloc"); + CHECK(block[5] == 0, "calloc zero-init"); + free(block); +} + +/* Test 2: Struct and union */ +struct Point { + double x; + double y; +}; + +double distance(struct Point *a, struct Point *b) +{ + double dx = a->x - b->x; + double dy = a->y - b->y; + return sqrt(dx * dx + dy * dy); +} + +void test_struct(void) +{ + struct Point p1 = {0.0, 0.0}; + struct Point p2 = {3.0, 4.0}; + double d = distance(&p1, &p2); + CHECK(fabs(d - 5.0) < 0.001, "struct distance"); + + union { + uint32_t u32; + uint8_t u8[4]; + } u; + u.u32 = 0x01020304; + CHECK(u.u8[0] == 0x04 || u.u8[0] == 0x01, "union endian check"); +} + +/* Test 3: Function pointers */ +int add(int a, int b) { return a + b; } +int mul(int a, int b) { return a * b; } + +void test_func_pointer(void) +{ + int (*ops[2])(int, int) = {add, mul}; + CHECK(ops[0](2, 3) == 5, "func ptr add"); + CHECK(ops[1](2, 3) == 6, "func ptr mul"); +} + +/* Test 4: Bit operations */ +void test_bitops(void) +{ + unsigned int val = 0xA5; + CHECK((val & 0xF0) == 0xA0, "bit AND"); + CHECK((val | 0x0F) == 0xAF, "bit OR"); + CHECK((val ^ 0xFF) == 0x5A, "bit XOR"); + CHECK((val << 4) == 0xA50, "bit shift left"); + CHECK((val >> 4) == 0x0A, "bit shift right"); +} + +/* Test 5: String operations */ +void test_string(void) +{ + char buf[64]; + snprintf(buf, sizeof(buf), "%s %s", "hello", "world"); + CHECK(strcmp(buf, "hello world") == 0, "snprintf"); + CHECK(strlen(buf) == 11, "strlen"); + + char *p = strstr(buf, "world"); + CHECK(p != NULL, "strstr"); + CHECK(p == buf + 6, "strstr offset"); +} + +int main(void) +{ + test_pointer_memory(); + test_struct(); + test_func_pointer(); + test_bitops(); + test_string(); + + if (errors == 0) { + printf("hello world\n"); + } else { + fprintf(stderr, "%d gcc tests failed\n", errors); + return 1; + } + return 0; +} diff --git a/os_smoke_src/testcases/develop/compiler/gxx_tc001.sh b/os_smoke_src/testcases/develop/compiler/gxx_tc001.sh new file mode 100755 index 0000000..8a625d5 --- /dev/null +++ b/os_smoke_src/testcases/develop/compiler/gxx_tc001.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: gxx_tc001 +# 描述: 验证 G++ C++ 编译器安装及基本编译执行功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: g++ 已安装 +# 步骤: +# 1. 检查 g++ 版本信息 +# 2. 用 g++ 编译测试程序 gxx_test.cpp +# 3. 执行编译产物并验证输出 +# 预期结果: +# 1. 版本信息正常输出 +# 2. 编译成功 +# 3. 输出包含 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 G++ C++ 编译器安装及基本编译执行功能" +tcnt=3 + +WORK_DIR="" + +setup() +{ + ensure_command "g++" "gcc-c++" + WORK_DIR=$(mktemp -d) + cp "$(dirname "$0")/gxx_test.cpp" "$WORK_DIR/" +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 g++ 版本" + local ver + ver=$(g++ --version 2>&1 | head -1) + if [ -n "$ver" ]; then + tst_res $TPASS "g++ 版本: $ver" + else + tst_res $TFAIL "无法获取 g++ 版本" + fi + ;; + 1) + tst_res $TINFO "编译 gxx_test.cpp" + g++ -std=c++11 "$WORK_DIR/gxx_test.cpp" -o "$WORK_DIR/gxx_test" 2>&1 + if [ $? -eq 0 ] && [ -x "$WORK_DIR/gxx_test" ]; then + tst_res $TPASS "g++ 编译成功" + else + tst_res $TFAIL "g++ 编译失败" + fi + ;; + 2) + tst_res $TINFO "执行编译产物并验证输出" + local output + output=$("$WORK_DIR/gxx_test" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "输出正确: $output" + else + tst_res $TFAIL "输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/compiler/gxx_test.cpp b/os_smoke_src/testcases/develop/compiler/gxx_test.cpp new file mode 100755 index 0000000..0da2ff8 --- /dev/null +++ b/os_smoke_src/testcases/develop/compiler/gxx_test.cpp @@ -0,0 +1,111 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +class SmartContainer { +public: + void add(const T& item) { data_.push_back(item); } + size_t size() const { return data_.size(); } + T sum() const { return std::accumulate(data_.begin(), data_.end(), T{}); } + std::vector sorted() const { + std::vector result = data_; + std::sort(result.begin(), result.end()); + return result; + } +private: + std::vector data_; +}; + +class Base { +public: + virtual ~Base() = default; + virtual std::string name() const = 0; +}; + +class Derived : public Base { +public: + std::string name() const override { return "Derived"; } +}; + +int main() +{ + int errors = 0; + + // Test 1: STL containers and algorithms + SmartContainer container; + container.add(3); + container.add(1); + container.add(2); + if (container.size() != 3 || container.sum() != 6) { + std::cerr << "FAIL: STL container test" << std::endl; + errors++; + } + auto s = container.sorted(); + if (s[0] != 1 || s[1] != 2 || s[2] != 3) { + std::cerr << "FAIL: sort test" << std::endl; + errors++; + } + + // Test 2: Smart pointers and polymorphism + std::unique_ptr ptr(new Derived()); + if (ptr->name() != "Derived") { + std::cerr << "FAIL: polymorphism test" << std::endl; + errors++; + } + + // Test 3: Lambda and std::function + std::function add = [](int a, int b) -> int { return a + b; }; + if (add(2, 3) != 5) { + std::cerr << "FAIL: lambda test" << std::endl; + errors++; + } + + // Test 4: Exception handling + try { + throw std::runtime_error("test exception"); + std::cerr << "FAIL: exception not thrown" << std::endl; + errors++; + } catch (const std::runtime_error& e) { + if (std::string(e.what()) != "test exception") { + std::cerr << "FAIL: exception message mismatch" << std::endl; + errors++; + } + } + + // Test 5: String stream + std::ostringstream oss; + oss << "value=" << 42; + if (oss.str() != "value=42") { + std::cerr << "FAIL: stringstream test" << std::endl; + errors++; + } + + // Test 6: Auto and range-based for + std::vector words = {"hello", " ", "world"}; + std::string combined; + for (const auto& w : words) { + combined += w; + } + + if (errors == 0 && combined == "hello world") { + std::cout << "hello world" << std::endl; + } else { + std::cerr << "FAIL: " << errors << " C++ feature tests failed" << std::endl; + return 1; + } + + return 0; +} diff --git a/os_smoke_src/testcases/develop/debug/Makefile b/os_smoke_src/testcases/develop/debug/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/develop/debug/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/develop/debug/attach.c b/os_smoke_src/testcases/develop/debug/attach.c new file mode 100755 index 0000000..d9f899a --- /dev/null +++ b/os_smoke_src/testcases/develop/debug/attach.c @@ -0,0 +1,19 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +#include +#include +int should_exit = 0; +int main () +{ + int local_i = 0; + alarm (60); + while (! should_exit) + { + local_i++; + } + return 0; /* postloop */ +} diff --git a/os_smoke_src/testcases/develop/debug/gdb_tc001.sh b/os_smoke_src/testcases/develop/debug/gdb_tc001.sh new file mode 100755 index 0000000..9ff1328 --- /dev/null +++ b/os_smoke_src/testcases/develop/debug/gdb_tc001.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: gdb_tc001 +# 描述: 验证 GDB 调试器 attach 调试及变量查看修改功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: gdb 和 gcc 已安装 +# 步骤: +# 1. 检查 gdb 版本 +# 2. 编译带调试信息的测试程序 +# 3. 后台运行测试程序并 gdb attach 查看/修改变量 +# 预期结果: +# 1. gdb 版本正常 +# 2. 编译成功 +# 3. gdb attach 成功,变量查看和修改正常 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC="验证 GDB 调试器 attach 调试及变量查看修改功能" +tcnt=3 + +WORK_DIR="" +ATTACH_PID="" + +setup() +{ + ensure_command "gdb" + ensure_command "gcc" + WORK_DIR=$(mktemp -d) + cp "$(dirname "$0")/attach.c" "$WORK_DIR/" + cp "$(dirname "$0")/gdb_test.ini" "$WORK_DIR/" +} + +cleanup() +{ + if [ -n "$ATTACH_PID" ]; then + kill -9 "$ATTACH_PID" 2>/dev/null + wait "$ATTACH_PID" 2>/dev/null + fi + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 gdb 版本" + local ver + ver=$(gdb --version 2>&1 | head -1) + if echo "$ver" | grep -qi "gdb"; then + tst_res $TPASS "gdb 版本: $ver" + else + tst_res $TFAIL "无法获取 gdb 版本" + fi + ;; + 1) + tst_res $TINFO "编译带调试信息的测试程序" + gcc -g "$WORK_DIR/attach.c" -o "$WORK_DIR/attach" 2>&1 + if [ $? -eq 0 ] && [ -x "$WORK_DIR/attach" ]; then + tst_res $TPASS "编译成功" + else + tst_res $TFAIL "编译失败" + fi + ;; + 2) + tst_res $TINFO "gdb attach 并查看/修改变量" + "$WORK_DIR/attach" & + ATTACH_PID=$! + sleep 1 + + if ! kill -0 "$ATTACH_PID" 2>/dev/null; then + tst_res $TFAIL "测试程序启动失败" + return + fi + + local gdb_output + gdb_output=$(gdb -batch -x "$WORK_DIR/gdb_test.ini" attach "$ATTACH_PID" 2>&1) + local gdb_ret=$? + + # 等待进程退出 + sleep 2 + if kill -0 "$ATTACH_PID" 2>/dev/null; then + kill -9 "$ATTACH_PID" 2>/dev/null + fi + wait "$ATTACH_PID" 2>/dev/null + ATTACH_PID="" + + if echo "$gdb_output" | grep -q "should_exit"; then + tst_res $TPASS "gdb attach 成功,变量操作正常" + else + tst_res $TFAIL "gdb 输出异常: $gdb_output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/debug/gdb_test.ini b/os_smoke_src/testcases/develop/debug/gdb_test.ini new file mode 100755 index 0000000..abd68d9 --- /dev/null +++ b/os_smoke_src/testcases/develop/debug/gdb_test.ini @@ -0,0 +1,14 @@ + +p should_exit +n +n +n +n +set should_exit=1 +n +p should_exit +n +n +n +q +y diff --git a/os_smoke_src/testcases/develop/debug/perf_tc001.sh b/os_smoke_src/testcases/develop/debug/perf_tc001.sh new file mode 100755 index 0000000..71b82fd --- /dev/null +++ b/os_smoke_src/testcases/develop/debug/perf_tc001.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: perf_tc001 +# 描述: 验证 perf 性能分析工具基本功能 +# 等级: P1 +# 用例类型: 功能测试 +# 前置条件: perf 可安装 +# 步骤: +# 1. 检查 perf 版本 +# 2. perf stat 统计简单命令性能 +# 3. perf list 列出可用事件 +# 预期结果: +# 1. perf 版本正常 +# 2. perf stat 输出包含性能计数器 +# 3. perf list 输出包含硬件/软件事件 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 perf 性能分析工具基本功能" +tcnt=3 + +setup() +{ + ensure_command "perf" +} + +cleanup() +{ + : +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 perf 版本" + local ver + ver=$(perf version 2>&1) + if [ -n "$ver" ]; then + tst_res $TPASS "perf 版本: $ver" + else + tst_res $TFAIL "无法获取 perf 版本" + fi + ;; + 1) + tst_res $TINFO "perf stat 统计性能" + local output + output=$(perf stat -- sleep 0.1 2>&1) + if echo "$output" | grep -qi "task-clock\|cycles\|instructions\|Performance counter"; then + tst_res $TPASS "perf stat 输出包含性能计数器" + else + tst_res $TFAIL "perf stat 输出异常: $output" + fi + ;; + 2) + tst_res $TINFO "perf list 列出可用事件" + local output + output=$(perf list 2>&1) + if echo "$output" | grep -qi "Hardware\|Software\|event"; then + tst_res $TPASS "perf list 输出包含事件列表" + else + tst_res $TFAIL "perf list 输出异常" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/debug/strace_tc001.sh b/os_smoke_src/testcases/develop/debug/strace_tc001.sh new file mode 100755 index 0000000..1eff3e1 --- /dev/null +++ b/os_smoke_src/testcases/develop/debug/strace_tc001.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: strace_tc001 +# 描述: 验证 strace 系统调用追踪工具基本功能 +# 等级: P1 +# 用例类型: 功能测试 +# 前置条件: strace 已安装 +# 步骤: +# 1. 检查 strace 版本 +# 2. strace 追踪简单命令并验证输出包含系统调用 +# 3. strace -c 统计模式验证 +# 预期结果: +# 1. strace 版本正常 +# 2. 输出包含 execve/write 等系统调用 +# 3. 统计信息包含 syscall 列 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC="验证 strace 系统调用追踪工具基本功能" +tcnt=3 + +setup() +{ + ensure_command "strace" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 strace 版本" + local ver + ver=$(strace --version 2>&1 | head -1) + if echo "$ver" | grep -qi "strace"; then + tst_res $TPASS "strace 版本: $ver" + else + tst_res $TFAIL "无法获取 strace 版本" + fi + ;; + 1) + tst_res $TINFO "strace 追踪 echo 命令" + local output + output=$(strace -f echo "hello" 2>&1) + if echo "$output" | grep -q "execve\|write"; then + tst_res $TPASS "strace 输出包含系统调用" + else + tst_res $TFAIL "strace 输出异常" + fi + ;; + 2) + tst_res $TINFO "strace -c 统计模式" + local output + output=$(strace -c ls /tmp 2>&1) + if echo "$output" | grep -q "syscall\|calls"; then + tst_res $TPASS "strace -c 统计输出正常" + else + tst_res $TFAIL "strace -c 统计输出异常" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/debug/valgrind_tc001.sh b/os_smoke_src/testcases/develop/debug/valgrind_tc001.sh new file mode 100755 index 0000000..903f0c7 --- /dev/null +++ b/os_smoke_src/testcases/develop/debug/valgrind_tc001.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: valgrind_tc001 +# 描述: 验证 Valgrind 内存检测工具基本功能 +# 等级: P1 +# 用例类型: 功能测试 +# 前置条件: valgrind 可安装,gcc 已安装 +# 步骤: +# 1. 检查 valgrind 版本 +# 2. 编译含内存泄漏的测试程序 +# 3. valgrind 检测内存泄漏 +# 预期结果: +# 1. valgrind 版本正常 +# 2. 编译成功 +# 3. valgrind 正确检测到内存泄漏 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 Valgrind 内存检测工具基本功能" +tcnt=3 + +WORK_DIR="" + +setup() +{ + ensure_command "gcc" + ensure_command "valgrind" + + WORK_DIR=$(mktemp -d) + + # 创建含内存泄漏的测试程序 + cat > "$WORK_DIR/leak_test.c" << 'CEOF' +#include +#include +int main() { + char *p = malloc(100); + printf("allocated %p\n", (void*)p); + /* 故意不 free,制造内存泄漏 */ + return 0; +} +CEOF +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 valgrind 版本" + local ver + ver=$(valgrind --version 2>&1) + if echo "$ver" | grep -qi "valgrind"; then + tst_res $TPASS "valgrind 版本: $ver" + else + tst_res $TFAIL "无法获取 valgrind 版本" + fi + ;; + 1) + tst_res $TINFO "编译内存泄漏测试程序" + gcc -g "$WORK_DIR/leak_test.c" -o "$WORK_DIR/leak_test" 2>&1 + if [ $? -eq 0 ] && [ -x "$WORK_DIR/leak_test" ]; then + tst_res $TPASS "编译成功" + else + tst_res $TFAIL "编译失败" + fi + ;; + 2) + tst_res $TINFO "valgrind 检测内存泄漏" + local output + output=$(valgrind --leak-check=full "$WORK_DIR/leak_test" 2>&1) + if echo "$output" | grep -qi "definitely lost\|LEAK SUMMARY"; then + tst_res $TPASS "valgrind 正确检测到内存泄漏" + else + tst_res $TFAIL "valgrind 未检测到预期的内存泄漏" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/editor/Makefile b/os_smoke_src/testcases/develop/editor/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/develop/editor/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/develop/editor/emacs_tc001.sh b/os_smoke_src/testcases/develop/editor/emacs_tc001.sh new file mode 100755 index 0000000..fbb2d42 --- /dev/null +++ b/os_smoke_src/testcases/develop/editor/emacs_tc001.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: emacs_tc001 +# 描述: 验证 Emacs 编辑器安装及 batch 模式基本功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: emacs-nox 可安装 +# 步骤: +# 1. 安装 emacs-nox 并检查版本 +# 2. 使用 batch 模式写入文件 +# 3. 验证文件内容 +# 预期结果: +# 1. emacs 可用 +# 2. 文件写入成功 +# 3. 内容为 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 Emacs 编辑器安装及 batch 模式基本功能" +tcnt=2 + +WORK_DIR="" + +setup() +{ + WORK_DIR=$(mktemp -d) + ensure_command "emacs" "emacs-nox" +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 emacs 版本" + local ver + ver=$(emacs --version 2>&1 | head -1) + if echo "$ver" | grep -qi "emacs"; then + tst_res $TPASS "emacs 版本: $ver" + else + tst_res $TFAIL "无法获取 emacs 版本" + fi + ;; + 1) + tst_res $TINFO "使用 batch 模式写入文件并验证" + local testfile="$WORK_DIR/emacs_test.txt" + emacs --batch --eval "(progn (find-file \"$testfile\") (insert \"hello world\") (save-buffer))" 2>/dev/null + if [ -f "$testfile" ]; then + local content + content=$(cat "$testfile") + if echo "$content" | grep -qi "hello world"; then + tst_res $TPASS "emacs batch 模式写入成功,内容正确" + else + tst_res $TFAIL "文件内容异常: $content" + fi + else + tst_res $TFAIL "emacs 未生成文件" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/editor/nano_tc001.sh b/os_smoke_src/testcases/develop/editor/nano_tc001.sh new file mode 100755 index 0000000..b97031f --- /dev/null +++ b/os_smoke_src/testcases/develop/editor/nano_tc001.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: nano_tc001 +# 描述: 验证 nano 编辑器安装及基本功能 +# 等级: P1 +# 用例类型: 功能测试 +# 前置条件: nano 可安装 +# 步骤: +# 1. 安装 nano 并检查 --version 输出 +# 2. 检查 nano --help 输出 +# 预期结果: +# 1. nano --version 输出包含版本号 +# 2. nano --help 输出包含用法信息 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 nano 编辑器安装及基本功能" +tcnt=2 + +setup() +{ + ensure_command "nano" +} + +cleanup() +{ + : +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 nano --version 输出" + local ver + ver=$(nano --version 2>&1 | head -1) + if echo "$ver" | grep -qi "nano"; then + tst_res $TPASS "nano 版本: $ver" + else + tst_res $TFAIL "无法获取 nano 版本" + fi + ;; + 1) + tst_res $TINFO "检查 nano --help 输出" + local help_out + help_out=$(nano --help 2>&1) + if echo "$help_out" | grep -qi "usage\|option\|nano"; then + tst_res $TPASS "nano --help 输出正常" + else + tst_res $TFAIL "nano --help 输出异常" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/editor/vim_tc001.sh b/os_smoke_src/testcases/develop/editor/vim_tc001.sh new file mode 100755 index 0000000..41d06a3 --- /dev/null +++ b/os_smoke_src/testcases/develop/editor/vim_tc001.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: vim_tc001 +# 描述: 验证 Vim 编辑器安装及基本编辑功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: vim-enhanced 可安装 +# 步骤: +# 1. 安装 vim-enhanced 并检查版本 +# 2. 使用 ex 模式写入文件 +# 3. 验证文件内容 +# 预期结果: +# 1. vim 可用 +# 2. 文件写入成功 +# 3. 内容为 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 Vim 编辑器安装及基本编辑功能" +tcnt=2 + +WORK_DIR="" + +setup() +{ + WORK_DIR=$(mktemp -d) + ensure_command "vim" "vim-enhanced" +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 vim 版本和帮助信息" + local ver + ver=$(vim --version 2>&1 | head -1) + if echo "$ver" | grep -qi "VIM\|Vi IMproved"; then + tst_res $TPASS "vim 版本: $ver" + else + tst_res $TFAIL "无法获取 vim 版本" + fi + ;; + 1) + tst_res $TINFO "使用 ex 模式写入文件并验证" + local testfile="$WORK_DIR/vim_test.txt" + vim -e -s "$testfile" << 'VIMEOF' +i +hello world +. +wq +VIMEOF + if [ -f "$testfile" ]; then + local content + content=$(cat "$testfile") + if echo "$content" | grep -qi "hello world"; then + tst_res $TPASS "vim ex 模式写入成功,内容正确" + else + tst_res $TFAIL "文件内容异常: $content" + fi + else + tst_res $TFAIL "vim 未生成文件" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/graphlib/Makefile b/os_smoke_src/testcases/develop/graphlib/Makefile new file mode 100755 index 0000000..1630244 --- /dev/null +++ b/os_smoke_src/testcases/develop/graphlib/Makefile @@ -0,0 +1,56 @@ +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CFLAGS += -g +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 各图形库的编译参数 +CAIRO_CFLAGS := $(shell pkg-config --cflags cairo 2>/dev/null) +CAIRO_LDFLAGS := $(shell pkg-config --libs cairo 2>/dev/null) +ifneq ($(CAIRO_LDFLAGS),) +CAIRO_LDFLAGS += -lm +endif +GTK_CFLAGS := $(shell pkg-config --cflags gtk+-3.0 2>/dev/null) +GTK_LDFLAGS := $(shell pkg-config --libs gtk+-3.0 2>/dev/null) + +TARGETS := +OBJECTS := + +ifneq ($(wildcard cairo_test.c),) +ifneq ($(CAIRO_LDFLAGS),) +TARGETS += cairo_test +OBJECTS += cairo_test.o +endif +endif + +ifneq ($(wildcard gtk_test.c),) +ifneq ($(GTK_CFLAGS),) +TARGETS += gtk_test +OBJECTS += gtk_test.o +endif +endif + +all: $(TARGETS) + @if [ -z "$(TARGETS)" ]; then echo "No .c files found, skipping compilation"; fi + +cairo_test.o: cairo_test.c + $(CC) $(CFLAGS) $(CAIRO_CFLAGS) -c $< -o $@ + +cairo_test: cairo_test.o + $(CC) $^ -o $@ $(CAIRO_LDFLAGS) + +gtk_test.o: gtk_test.c + $(CC) $(CFLAGS) $(GTK_CFLAGS) -c $< -o $@ + +gtk_test: gtk_test.o + $(CC) $^ -o $@ $(GTK_LDFLAGS) + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(OBJECTS),) + rm -rfv $(OBJECTS) $(TARGETS) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/develop/graphlib/cairo_tc001.sh b/os_smoke_src/testcases/develop/graphlib/cairo_tc001.sh new file mode 100755 index 0000000..69ff02b --- /dev/null +++ b/os_smoke_src/testcases/develop/graphlib/cairo_tc001.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cairo_tc001 +# 描述: 验证 Cairo 2D 图形库编译链接及 PNG 生成功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: cairo-devel 可安装,gcc 已安装 +# 步骤: +# 1. 检查 cairo 版本信息 +# 2. 使用 pkg-config 获取编译参数并编译测试程序 +# 3. 执行并验证生成 PNG 文件 +# 预期结果: +# 1. cairo 版本正常 +# 2. 编译成功 +# 3. 生成 hello.png 且文件大小 > 0 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC="验证 Cairo 2D 图形库编译链接及 PNG 生成功能" +tcnt=3 + +WORK_DIR="" +SCRIPT_DIR="" +CAIRO_INSTALLED=0 + +setup() +{ + SCRIPT_DIR=$(dirname "$0") + ensure_command "gcc" + WORK_DIR=$(mktemp -d) + cp "$SCRIPT_DIR/cairo_test.c" "$WORK_DIR/" + + if ! pkg-config --exists cairo 2>/dev/null; then + tst_res $TINFO "cairo-devel 未安装,尝试安装" + yum install -y cairo cairo-devel >/dev/null 2>&1 + if ! pkg-config --exists cairo 2>/dev/null; then + tst_brk $TCONF "cairo-devel 安装失败" + fi + CAIRO_INSTALLED=1 + fi +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" + if [ "$CAIRO_INSTALLED" -eq 1 ]; then + remove_packages "cairo-devel" + fi +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 cairo pkg-config 信息" + local ver + ver=$(pkg-config --modversion cairo 2>&1) + if [ -n "$ver" ]; then + tst_res $TPASS "cairo 版本: $ver" + else + tst_res $TFAIL "无法获取 cairo 版本" + fi + ;; + 1) + tst_res $TINFO "编译 cairo 测试程序" + local cflags ldflags + cflags=$(pkg-config --cflags cairo 2>&1) + ldflags=$(pkg-config --libs cairo 2>&1) + gcc $cflags "$WORK_DIR/cairo_test.c" -o "$WORK_DIR/cairo_test" $ldflags 2>&1 + if [ $? -eq 0 ] && [ -x "$WORK_DIR/cairo_test" ]; then + tst_res $TPASS "cairo 测试程序编译成功" + else + tst_res $TFAIL "cairo 测试程序编译失败" + fi + ;; + 2) + tst_res $TINFO "执行并验证 PNG 生成" + cd "$WORK_DIR" || return + ./cairo_test 2>&1 + if [ -f "$WORK_DIR/hello.png" ] && [ -s "$WORK_DIR/hello.png" ]; then + tst_res $TPASS "成功生成 hello.png ($(wc -c < "$WORK_DIR/hello.png") bytes)" + else + tst_res $TFAIL "未生成 hello.png 或文件为空" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/graphlib/cairo_test.c b/os_smoke_src/testcases/develop/graphlib/cairo_test.c new file mode 100755 index 0000000..341a015 --- /dev/null +++ b/os_smoke_src/testcases/develop/graphlib/cairo_test.c @@ -0,0 +1,184 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +/* + * 文件名: dev_lib_cairo_tc001.c + * 功能描述: + * 使用 Cairo 2D 图形库绘制一张简单的 PNG 图片, + * 通过创建 surface、context、渐变背景、参考网格以及文本等操作, + * 用于验证 cairo 开发库是否安装可用。 + * + * 设计说明: + * - 输出文件固定为 hello.png,方便现有测试脚本检查。 + * - 图片内容包含 "Hello, world" 文本,便于肉眼确认。 + * - 代码结构刻意拆分为多个绘制函数,避免与任何官方示例结构雷同。 + */ + +#include +#include +#include +#include + +#define CAIRO_TEST_WIDTH 480 +#define CAIRO_TEST_HEIGHT 240 +#define CAIRO_TEST_OUTPUT "hello.png" + +/* 绘制渐变背景 */ +static void +draw_background(cairo_t *cr) +{ + cairo_pattern_t *pat; + + pat = cairo_pattern_create_linear(0.0, 0.0, + 0.0, CAIRO_TEST_HEIGHT); + /* 顶部偏深色、到底部逐渐变亮 */ + cairo_pattern_add_color_stop_rgb(pat, 0.0, 0.06, 0.12, 0.30); + cairo_pattern_add_color_stop_rgb(pat, 0.5, 0.15, 0.25, 0.55); + cairo_pattern_add_color_stop_rgb(pat, 1.0, 0.80, 0.85, 0.95); + + cairo_rectangle(cr, 0.0, 0.0, + CAIRO_TEST_WIDTH, CAIRO_TEST_HEIGHT); + cairo_set_source(cr, pat); + cairo_fill(cr); + + cairo_pattern_destroy(pat); +} + +/* 绘制简单的参考网格,便于肉眼观察渲染效果 */ +static void +draw_grid(cairo_t *cr) +{ + int step = 40; + int x, y; + + cairo_save(cr); + + cairo_set_line_width(cr, 0.5); + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.30); + + for (x = step; x < CAIRO_TEST_WIDTH; x += step) { + cairo_move_to(cr, (double)x, 0.0); + cairo_line_to(cr, (double)x, CAIRO_TEST_HEIGHT); + } + for (y = step; y < CAIRO_TEST_HEIGHT; y += step) { + cairo_move_to(cr, 0.0, (double)y); + cairo_line_to(cr, CAIRO_TEST_WIDTH, (double)y); + } + + cairo_stroke(cr); + + cairo_restore(cr); +} + +/* 绘制中心圆形和边框,展示基本矢量图形能力 */ +static void +draw_shapes(cairo_t *cr) +{ + double cx = CAIRO_TEST_WIDTH / 2.0; + double cy = CAIRO_TEST_HEIGHT / 2.0; + double radius = 70.0; + + cairo_save(cr); + + /* 外圈 */ + cairo_set_line_width(cr, 4.0); + cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 0.85); + cairo_arc(cr, cx, cy, radius, 0.0, 2 * M_PI); + cairo_stroke(cr); + + /* 内部填充一个略微偏亮的圆 */ + cairo_set_source_rgba(cr, 0.9, 0.9, 1.0, 0.85); + cairo_arc(cr, cx, cy, radius - 6.0, 0.0, 2 * M_PI); + cairo_fill(cr); + + cairo_restore(cr); +} + +/* 绘制 "Hello, world" 文本(带阴影) */ +static void +draw_hello_text(cairo_t *cr) +{ + const char *text = "Hello, world"; + cairo_text_extents_t extents; + double x, y; + + cairo_save(cr); + + cairo_select_font_face(cr, + "Sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size(cr, 32.0); + + cairo_text_extents(cr, text, &extents); + + x = (CAIRO_TEST_WIDTH - extents.width) / 2.0 - extents.x_bearing; + y = (CAIRO_TEST_HEIGHT - extents.height) / 2.0 - extents.y_bearing; + + /* 阴影 */ + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.6); + cairo_move_to(cr, x + 2.0, y + 2.0); + cairo_show_text(cr, text); + + /* 主文字 */ + cairo_set_source_rgb(cr, 0.1, 0.2, 0.8); + cairo_move_to(cr, x, y); + cairo_show_text(cr, text); + + cairo_restore(cr); +} + +int +main(int argc, char *argv[]) +{ + (void)argc; + (void)argv; + + cairo_surface_t *surface = NULL; + cairo_t *cr = NULL; + cairo_status_t status; + + /* 创建 ARGB32 图像 surface */ + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + CAIRO_TEST_WIDTH, + CAIRO_TEST_HEIGHT); + status = cairo_surface_status(surface); + if (status != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, "[cairo] failed to create surface: %s\n", + cairo_status_to_string(status)); + return EXIT_FAILURE; + } + + cr = cairo_create(surface); + status = cairo_status(cr); + if (status != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, "[cairo] failed to create context: %s\n", + cairo_status_to_string(status)); + cairo_surface_destroy(surface); + return EXIT_FAILURE; + } + + /* 分步骤绘制图形 */ + draw_background(cr); + draw_grid(cr); + draw_shapes(cr); + draw_hello_text(cr); + + /* 将内存 surface 写出到 PNG 文件 */ + status = cairo_surface_write_to_png(surface, CAIRO_TEST_OUTPUT); + if (status != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, "[cairo] failed to write png '%s': %s\n", + CAIRO_TEST_OUTPUT, cairo_status_to_string(status)); + cairo_destroy(cr); + cairo_surface_destroy(surface); + return EXIT_FAILURE; + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); + + return EXIT_SUCCESS; +} diff --git a/os_smoke_src/testcases/develop/graphlib/gtk_tc001.sh b/os_smoke_src/testcases/develop/graphlib/gtk_tc001.sh new file mode 100755 index 0000000..da1d7fa --- /dev/null +++ b/os_smoke_src/testcases/develop/graphlib/gtk_tc001.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: gtk_tc001 +# 描述: 验证 GTK3 开发库编译链接功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: gtk3-devel 可安装,gcc 已安装 +# 步骤: +# 1. 检查 GTK3 版本信息 +# 2. 编译 GTK3 测试程序 +# 预期结果: +# 1. pkg-config 可获取 GTK3 版本 +# 2. 编译成功(无需执行,需 UI 环境) +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC="验证 GTK3 开发库编译链接功能" +tcnt=2 + +WORK_DIR="" +SCRIPT_DIR="" +GTK_INSTALLED=0 + +setup() +{ + SCRIPT_DIR=$(dirname "$0") + ensure_command "gcc" + WORK_DIR=$(mktemp -d) + cp "$SCRIPT_DIR/gtk_test.c" "$WORK_DIR/" + + if ! pkg-config --exists gtk+-3.0 2>/dev/null; then + tst_res $TINFO "gtk3-devel 未安装,尝试安装" + yum install -y gtk3-devel >/dev/null 2>&1 + if ! pkg-config --exists gtk+-3.0 2>/dev/null; then + tst_brk $TCONF "gtk3-devel 安装失败" + fi + GTK_INSTALLED=1 + fi +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" + if [ "$GTK_INSTALLED" -eq 1 ]; then + remove_packages "gtk3-devel" + fi +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 GTK3 pkg-config 信息" + local ver + ver=$(pkg-config --modversion gtk+-3.0 2>&1) + if [ -n "$ver" ]; then + tst_res $TPASS "GTK3 版本: $ver" + else + tst_res $TFAIL "无法获取 GTK3 版本" + fi + ;; + 1) + tst_res $TINFO "编译 GTK3 测试程序" + local cflags ldflags + cflags=$(pkg-config --cflags gtk+-3.0 2>&1) + ldflags=$(pkg-config --libs gtk+-3.0 2>&1) + gcc $cflags "$WORK_DIR/gtk_test.c" -o "$WORK_DIR/gtk_test" $ldflags 2>&1 + if [ $? -eq 0 ] && [ -x "$WORK_DIR/gtk_test" ]; then + tst_res $TPASS "GTK3 测试程序编译成功(需 UI 环境执行)" + else + tst_res $TFAIL "GTK3 测试程序编译失败" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/graphlib/gtk_test.c b/os_smoke_src/testcases/develop/graphlib/gtk_test.c new file mode 100755 index 0000000..9575d66 --- /dev/null +++ b/os_smoke_src/testcases/develop/graphlib/gtk_test.c @@ -0,0 +1,134 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +/* + * 文件名: gtk_test.c + * 功能描述: 使用 GTK+3 创建一个简单的计数器窗口,用于验证 GTK 开发库是否可用 + * 说明: 本示例为自定义测试程序,不依赖官方示例代码结构 + */ + +#include + +/* + * 简单的应用数据结构,用于在回调之间传递状态 + */ +typedef struct _AppWidgets { + GtkWidget *label; /* 显示点击次数的标签 */ + guint count; /* 按钮点击次数 */ +} AppWidgets; + +/* 按钮点击回调:更新计数并刷新标签文本 */ +static void +on_button_clicked (GtkWidget *button, + gpointer user_data) +{ + AppWidgets *widgets = (AppWidgets *)user_data; + gchar *text = NULL; + + widgets->count++; + + text = g_strdup_printf ("按钮已点击 %u 次", widgets->count); + gtk_label_set_text (GTK_LABEL (widgets->label), text); + g_free (text); + + (void)button; /* 未使用参数,避免编译告警 */ +} + +/* 退出菜单项回调:主动退出应用 */ +static void +on_menu_quit (GtkMenuItem *menuitem, + gpointer user_data) +{ + GtkApplication *app = GTK_APPLICATION (user_data); + + (void)menuitem; + + g_application_quit (G_APPLICATION (app)); +} + +/* + * 应用激活回调:创建主窗口、菜单栏和按钮等控件 + */ +static void +activate (GtkApplication *app, + gpointer user_data) +{ + GtkWidget *window = NULL; + GtkWidget *vbox = NULL; + GtkWidget *menubar = NULL; + GtkWidget *file_menu = NULL; + GtkWidget *file_item = NULL; + GtkWidget *quit_item = NULL; + GtkWidget *button = NULL; + AppWidgets *widgets = NULL; + + (void)user_data; + + widgets = g_new0 (AppWidgets, 1); + + /* 创建主窗口 */ + window = gtk_application_window_new (app); + gtk_window_set_title (GTK_WINDOW (window), "GTK 开发库测试"); + gtk_window_set_default_size (GTK_WINDOW (window), 320, 200); + + /* 垂直布局:上方菜单栏,下方主体内容 */ + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (window), vbox); + + /* 创建菜单栏 */ + menubar = gtk_menu_bar_new (); + file_menu = gtk_menu_new (); + file_item = gtk_menu_item_new_with_label ("文件"); + quit_item = gtk_menu_item_new_with_label ("退出"); + + gtk_menu_shell_append (GTK_MENU_SHELL (file_menu), quit_item); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (file_item), file_menu); + gtk_menu_shell_append (GTK_MENU_SHELL (menubar), file_item); + + gtk_box_pack_start (GTK_BOX (vbox), menubar, FALSE, FALSE, 0); + + /* 中间区域:标签 + 按钮 */ + widgets->label = gtk_label_new ("按钮尚未被点击"); + gtk_box_pack_start (GTK_BOX (vbox), widgets->label, TRUE, TRUE, 12); + + button = gtk_button_new_with_label ("点击我"); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 12); + + /* 信号连接 */ + g_signal_connect (button, + "clicked", + G_CALLBACK (on_button_clicked), + widgets); + + g_signal_connect (quit_item, + "activate", + G_CALLBACK (on_menu_quit), + app); + + g_signal_connect_swapped (window, + "destroy", + G_CALLBACK (g_free), + widgets); + + gtk_widget_show_all (window); +} + +int +main (int argc, + char *argv[]) +{ + GtkApplication *app; + int status; + + /* 应用 ID 使用自定义命名空间,避免与官方示例重复 */ + app = gtk_application_new ("org.tencentos.gtk.test", G_APPLICATION_FLAGS_NONE); + g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); + + status = g_application_run (G_APPLICATION (app), argc, argv); + g_object_unref (app); + + return status; +} diff --git a/os_smoke_src/testcases/develop/graphlib/opengl_tc001.sh b/os_smoke_src/testcases/develop/graphlib/opengl_tc001.sh new file mode 100755 index 0000000..b0c39f3 --- /dev/null +++ b/os_smoke_src/testcases/develop/graphlib/opengl_tc001.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: opengl_tc001 +# 描述: 验证 OpenGL (freeglut/Mesa) 开发库编译链接功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: mesa-libGL-devel 和 freeglut-devel 可安装 +# 步骤: +# 1. 检查 libGL 库文件是否存在 +# 2. 编译 OpenGL 测试程序 +# 预期结果: +# 1. libGL 库文件存在 +# 2. 编译成功(需 UI 环境执行) +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC="验证 OpenGL (freeglut/Mesa) 开发库编译链接功能" +tcnt=2 + +WORK_DIR="" +SCRIPT_DIR="" +GL_INSTALLED=0 + +setup() +{ + SCRIPT_DIR=$(dirname "$0") + ensure_command "g++" "gcc-c++" + WORK_DIR=$(mktemp -d) + cp "$SCRIPT_DIR/opengl_test.cpp" "$WORK_DIR/" + + if ! rpm -q mesa-libGL-devel >/dev/null 2>&1 || ! rpm -q freeglut-devel >/dev/null 2>&1; then + tst_res $TINFO "OpenGL 开发库未安装,尝试安装" + yum install -y mesa-libGL-devel freeglut-devel >/dev/null 2>&1 + if ! rpm -q mesa-libGL-devel >/dev/null 2>&1 || ! rpm -q freeglut-devel >/dev/null 2>&1; then + tst_brk $TCONF "mesa-libGL-devel 或 freeglut-devel 安装失败" + fi + GL_INSTALLED=1 + fi +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" + if [ "$GL_INSTALLED" -eq 1 ]; then + remove_packages "mesa-libGL-devel freeglut-devel" + fi +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 OpenGL 库文件" + if ldconfig -p 2>/dev/null | grep -q "libGL"; then + tst_res $TPASS "libGL 库文件存在" + else + tst_res $TFAIL "libGL 库文件不存在" + fi + ;; + 1) + tst_res $TINFO "编译 OpenGL 测试程序" + g++ "$WORK_DIR/opengl_test.cpp" -o "$WORK_DIR/opengl_test" -lglut -lGL -lGLU 2>&1 + if [ $? -eq 0 ] && [ -x "$WORK_DIR/opengl_test" ]; then + tst_res $TPASS "OpenGL 测试程序编译成功(需 UI 环境执行)" + else + tst_res $TFAIL "OpenGL 测试程序编译失败" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/graphlib/opengl_test.cpp b/os_smoke_src/testcases/develop/graphlib/opengl_test.cpp new file mode 100755 index 0000000..6e1ddbe --- /dev/null +++ b/os_smoke_src/testcases/develop/graphlib/opengl_test.cpp @@ -0,0 +1,45 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +/* +* Compiler parameters: +* -framework GLUT +* -framework OpenGL +*/ + +#if defined(__APPLE__) +#include +#else +#include +#endif + +// Displays a window with a square inside. + +void display(void) +{ + glClear(GL_COLOR_BUFFER_BIT); + + glBegin(GL_POLYGON); + glVertex3f(0.0, 0.0, 0.0); + glVertex3f(0.5, 0.0, 0.0); + glVertex3f(0.5, 0.5, 0.0); + glVertex3f(0.0, 0.5, 0.0); + glEnd(); + + glFlush(); +} + +int main(int argc, char** argv) +{ + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_SINGLE); // Use single display buffer. + glutInitWindowSize(300, 300); + glutInitWindowPosition(100, 100); + glutCreateWindow("Hello world"); + glutDisplayFunc(display); + glutMainLoop(); + return 0; +} diff --git a/os_smoke_src/testcases/develop/graphlib/qt_tc001.sh b/os_smoke_src/testcases/develop/graphlib/qt_tc001.sh new file mode 100755 index 0000000..328a23e --- /dev/null +++ b/os_smoke_src/testcases/develop/graphlib/qt_tc001.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: qt_tc001 +# 描述: 验证 Qt5 开发环境 qmake 构建编译执行功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: qt5-qtbase-devel 可安装 +# 步骤: +# 1. 安装 qt5-qtbase-devel 并检查 qmake 版本 +# 2. qmake 生成 Makefile 并编译 +# 3. 执行并验证输出 +# 预期结果: +# 1. qmake 可用 +# 2. 编译成功 +# 3. 输出包含 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 Qt5 开发环境 qmake 构建编译执行功能" +tcnt=3 + +WORK_DIR="" +SCRIPT_DIR="" + +setup() +{ + SCRIPT_DIR=$(dirname "$0") + + ensure_command "g++" "gcc-c++" + ensure_command "qmake-qt5" "qt5-qtbase-devel" + + WORK_DIR=$(mktemp -d) + cp "$SCRIPT_DIR/qt_test/qt_test.cpp" "$WORK_DIR/" + + # 创建 .pro 文件 + cat > "$WORK_DIR/qt_test.pro" << 'PROEOF' +QT += core +QT -= gui +TARGET = qt_test +CONFIG += console +CONFIG -= app_bundle +SOURCES += qt_test.cpp +PROEOF +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" + remove_packages "qt5-qtbase-devel" +} + +run_test() +{ + local qmake_cmd="qmake-qt5" + which qmake-qt5 >/dev/null 2>&1 || qmake_cmd="qmake" + + case $1 in + 0) + tst_res $TINFO "检查 qmake 版本" + local ver + ver=$($qmake_cmd --version 2>&1) + if echo "$ver" | grep -qi "qmake\|Qt"; then + tst_res $TPASS "qmake 版本: $(echo "$ver" | tail -1)" + else + tst_res $TFAIL "无法获取 qmake 版本" + fi + ;; + 1) + tst_res $TINFO "qmake 生成 Makefile 并编译" + cd "$WORK_DIR" || return + $qmake_cmd qt_test.pro 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "qmake 生成 Makefile 失败" + return + fi + make 2>&1 + if [ $? -eq 0 ] && [ -x "$WORK_DIR/qt_test" ]; then + tst_res $TPASS "Qt5 项目编译成功" + else + tst_res $TFAIL "Qt5 项目编译失败" + fi + ;; + 2) + tst_res $TINFO "执行并验证输出" + local output + output=$("$WORK_DIR/qt_test" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "输出正确: $output" + else + tst_res $TFAIL "输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/graphlib/qt_test/.qmake.stash b/os_smoke_src/testcases/develop/graphlib/qt_test/.qmake.stash new file mode 100755 index 0000000..97ddbc9 --- /dev/null +++ b/os_smoke_src/testcases/develop/graphlib/qt_test/.qmake.stash @@ -0,0 +1,22 @@ +QMAKE_CXX.QT_COMPILER_STDCXX = 201402L +QMAKE_CXX.QMAKE_GCC_MAJOR_VERSION = 8 +QMAKE_CXX.QMAKE_GCC_MINOR_VERSION = 5 +QMAKE_CXX.QMAKE_GCC_PATCH_VERSION = 0 +QMAKE_CXX.COMPILER_MACROS = \ + QT_COMPILER_STDCXX \ + QMAKE_GCC_MAJOR_VERSION \ + QMAKE_GCC_MINOR_VERSION \ + QMAKE_GCC_PATCH_VERSION +QMAKE_CXX.INCDIRS = \ + /usr/include/c++/8 \ + /usr/include/c++/8/x86_64-redhat-linux \ + /usr/include/c++/8/backward \ + /usr/lib/gcc/x86_64-redhat-linux/8/include \ + /usr/local/include \ + /usr/include +QMAKE_CXX.LIBDIRS = \ + /usr/lib/gcc/x86_64-redhat-linux/8 \ + /usr/lib64 \ + /lib64 \ + /usr/lib \ + /lib diff --git a/os_smoke_src/testcases/develop/graphlib/qt_test/Makefile b/os_smoke_src/testcases/develop/graphlib/qt_test/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/develop/graphlib/qt_test/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/develop/graphlib/qt_test/qt_test.cpp b/os_smoke_src/testcases/develop/graphlib/qt_test/qt_test.cpp new file mode 100755 index 0000000..ee0758f --- /dev/null +++ b/os_smoke_src/testcases/develop/graphlib/qt_test/qt_test.cpp @@ -0,0 +1,110 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int errors = 0; + +static void check(bool cond, const char *msg) +{ + if (!cond) { + QTextStream(stderr) << "FAIL: " << msg << endl; + errors++; + } +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + // Test 1: QString operations + QString s1 = "Hello"; + QString s2 = "World"; + QString combined = s1 + " " + s2; + check(combined == "Hello World", "QString concatenation"); + check(combined.toLower() == "hello world", "QString toLower"); + check(combined.contains("World"), "QString contains"); + check(combined.split(" ").size() == 2, "QString split"); + + // Test 2: QStringList + QStringList list; + list << "alpha" << "beta" << "gamma"; + check(list.size() == 3, "QStringList size"); + check(list.join(",") == "alpha,beta,gamma", "QStringList join"); + list.sort(); + check(list.first() == "alpha", "QStringList sort"); + + // Test 3: QMap + QMap map; + map["one"] = 1; + map["two"] = 2; + map["three"] = 3; + check(map.size() == 3, "QMap size"); + check(map.value("two") == 2, "QMap value"); + check(map.contains("three"), "QMap contains"); + + // Test 4: QTemporaryFile I/O + { + QTemporaryFile tmpFile; + check(tmpFile.open(), "QTemporaryFile open"); + QTextStream out(&tmpFile); + out << "qt file io test" << endl; + out.flush(); + tmpFile.seek(0); + QTextStream in(&tmpFile); + QString content = in.readLine(); + check(content == "qt file io test", "QTemporaryFile read/write"); + } + + // Test 5: QDateTime + QDateTime now = QDateTime::currentDateTime(); + check(now.isValid(), "QDateTime valid"); + QString dateStr = now.toString("yyyy-MM-dd"); + QRegularExpression dateRe("^\\d{4}-\\d{2}-\\d{2}$"); + check(dateRe.match(dateStr).hasMatch(), "QDateTime format"); + + // Test 6: QJsonDocument + QJsonObject obj; + obj["name"] = "test"; + obj["value"] = 42; + QJsonArray arr; + arr.append("a"); + arr.append("b"); + obj["items"] = arr; + QJsonDocument doc(obj); + QString jsonStr = doc.toJson(QJsonDocument::Compact); + check(jsonStr.contains("\"name\":\"test\""), "QJsonDocument serialize"); + QJsonDocument parsed = QJsonDocument::fromJson(jsonStr.toUtf8()); + check(!parsed.isNull(), "QJsonDocument parse"); + check(parsed.object()["value"].toInt() == 42, "QJsonDocument value"); + + // Test 7: QDir + QDir dir("/tmp"); + check(dir.exists(), "QDir /tmp exists"); + + if (errors == 0) { + QTextStream(stdout) << "hello world" << endl; + } else { + QTextStream(stderr) << errors << " Qt feature tests failed" << endl; + return 1; + } + + return 0; +} diff --git a/os_smoke_src/testcases/develop/language/Makefile b/os_smoke_src/testcases/develop/language/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/develop/language/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/develop/language/golang_tc001.sh b/os_smoke_src/testcases/develop/language/golang_tc001.sh new file mode 100755 index 0000000..490c6a2 --- /dev/null +++ b/os_smoke_src/testcases/develop/language/golang_tc001.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# +# 用例名称: golang_tc001 +# 描述: 验证 Go 语言环境安装及 fmt/run/build 功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: golang 可安装 +# 步骤: +# 1. 安装 golang 并检查版本 +# 2. go fmt 格式化检查 +# 3. go run 直接运行 +# 4. go build 编译并执行 +# 预期结果: +# 1. go 版本正常 +# 2. go fmt 成功 +# 3. 输出 hello world +# 4. 编译成功且输出 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 Go 语言环境安装及 fmt/run/build 功能" +tcnt=4 + +WORK_DIR="" +SCRIPT_DIR="" + +setup() +{ + SCRIPT_DIR=$(dirname "$0") + WORK_DIR=$(mktemp -d) + cp "$SCRIPT_DIR/gotest.go" "$WORK_DIR/" + + ensure_command "go" "golang" +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 go 版本" + local ver + ver=$(go version 2>&1) + if echo "$ver" | grep -q "go"; then + tst_res $TPASS "go 版本: $ver" + else + tst_res $TFAIL "无法获取 go 版本" + fi + ;; + 1) + tst_res $TINFO "go fmt 格式化检查" + go fmt "$WORK_DIR/gotest.go" >/dev/null 2>&1 + if [ $? -eq 0 ]; then + tst_res $TPASS "go fmt 成功" + else + tst_res $TFAIL "go fmt 失败" + fi + ;; + 2) + tst_res $TINFO "go run 直接运行" + local output + output=$(go run "$WORK_DIR/gotest.go" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "go run 输出正确: $output" + else + tst_res $TFAIL "go run 输出异常: $output" + fi + ;; + 3) + tst_res $TINFO "go build 编译并执行" + go build -o "$WORK_DIR/gotest" "$WORK_DIR/gotest.go" 2>&1 + if [ $? -ne 0 ] || [ ! -x "$WORK_DIR/gotest" ]; then + tst_res $TFAIL "go build 失败" + return + fi + local output + output=$("$WORK_DIR/gotest" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "go build 产物执行正确: $output" + else + tst_res $TFAIL "go build 产物输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/language/gotest.go b/os_smoke_src/testcases/develop/language/gotest.go new file mode 100755 index 0000000..2ae0e0f --- /dev/null +++ b/os_smoke_src/testcases/develop/language/gotest.go @@ -0,0 +1,123 @@ +// ############################################# +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2026 Tencent. All Rights Reserved. +// Author: kevinzcheng +// Date: 2026/03/11 +// ############################################# +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + "sync" +) + +func testGoroutineChannel() error { + ch := make(chan int, 5) + var wg sync.WaitGroup + for i := 0; i < 5; i++ { + wg.Add(1) + go func(n int) { + defer wg.Done() + ch <- n * n + }(i) + } + wg.Wait() + close(ch) + sum := 0 + for v := range ch { + sum += v + } + // 0+1+4+9+16 = 30 + if sum != 30 { + return fmt.Errorf("goroutine sum=%d, want 30", sum) + } + return nil +} + +func testJSON() error { + type Person struct { + Name string `json:"name"` + Age int `json:"age"` + } + p := Person{Name: "test", Age: 25} + data, err := json.Marshal(p) + if err != nil { + return fmt.Errorf("json marshal: %v", err) + } + var p2 Person + if err := json.Unmarshal(data, &p2); err != nil { + return fmt.Errorf("json unmarshal: %v", err) + } + if p2.Name != "test" || p2.Age != 25 { + return fmt.Errorf("json roundtrip mismatch: %+v", p2) + } + return nil +} + +func testFileIO() error { + tmpDir := os.TempDir() + tmpFile := filepath.Join(tmpDir, "go_smoke_test.txt") + content := "go file io test" + if err := ioutil.WriteFile(tmpFile, []byte(content), 0644); err != nil { + return fmt.Errorf("write file: %v", err) + } + defer os.Remove(tmpFile) + data, err := ioutil.ReadFile(tmpFile) + if err != nil { + return fmt.Errorf("read file: %v", err) + } + if string(data) != content { + return fmt.Errorf("content mismatch: got %q", string(data)) + } + return nil +} + +func testSliceSort() error { + nums := []int{5, 3, 1, 4, 2} + sort.Ints(nums) + for i := 0; i < len(nums)-1; i++ { + if nums[i] > nums[i+1] { + return fmt.Errorf("sort failed: %v", nums) + } + } + words := []string{"world", "hello"} + sort.Strings(words) + result := strings.Join(words, " ") + if result != "hello world" { + return fmt.Errorf("string sort: got %q", result) + } + return nil +} + +func main() { + tests := []struct { + name string + fn func() error + }{ + {"goroutine_channel", testGoroutineChannel}, + {"json", testJSON}, + {"file_io", testFileIO}, + {"slice_sort", testSliceSort}, + } + + errors := 0 + for _, t := range tests { + if err := t.fn(); err != nil { + fmt.Fprintf(os.Stderr, "FAIL: %s: %v\n", t.name, err) + errors++ + } + } + + if errors == 0 { + fmt.Print("hello world\n") + } else { + fmt.Fprintf(os.Stderr, "%d Go tests failed\n", errors) + os.Exit(1) + } +} diff --git a/os_smoke_src/testcases/develop/language/java_tc001.sh b/os_smoke_src/testcases/develop/language/java_tc001.sh new file mode 100755 index 0000000..4a84389 --- /dev/null +++ b/os_smoke_src/testcases/develop/language/java_tc001.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: java_tc001 +# 描述: 验证 Java 开发环境 javac 编译和 java 执行功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: java-devel 可安装 +# 步骤: +# 1. 安装 java-devel 并检查 javac/java 版本 +# 2. javac 编译测试程序 +# 3. java 执行并验证输出 +# 预期结果: +# 1. javac/java 可用 +# 2. 编译成功 +# 3. 输出 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 Java 开发环境 javac 编译和 java 执行功能" +tcnt=3 + +WORK_DIR="" +JAVA_INSTALLED=0 + +setup() +{ + WORK_DIR=$(mktemp -d) + cp "$(dirname "$0")/java_test.java" "$WORK_DIR/" + + if ! which javac >/dev/null 2>&1; then + install_packages "java-devel" || install_packages "java-1.8.0-openjdk-devel" + if ! which javac >/dev/null 2>&1; then + tst_brk $TCONF "java-devel 安装失败" + fi + JAVA_INSTALLED=1 + fi +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" + if [ "$JAVA_INSTALLED" -eq 1 ]; then + remove_packages "java-devel java-1.8.0-openjdk-devel" + fi +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 javac/java 版本" + local jcver jver + jcver=$(javac -version 2>&1) + jver=$(java -version 2>&1 | head -1) + if [ -n "$jcver" ] && [ -n "$jver" ]; then + tst_res $TPASS "javac: $jcver, java: $jver" + else + tst_res $TFAIL "javac 或 java 版本获取失败" + fi + ;; + 1) + tst_res $TINFO "javac 编译 java_test.java" + javac "$WORK_DIR/java_test.java" -d "$WORK_DIR/" 2>&1 + if [ $? -eq 0 ] && [ -f "$WORK_DIR/java_test.class" ]; then + tst_res $TPASS "javac 编译成功" + else + tst_res $TFAIL "javac 编译失败" + fi + ;; + 2) + tst_res $TINFO "java 执行并验证输出" + local output + output=$(cd "$WORK_DIR" && java java_test 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "输出正确: $output" + else + tst_res $TFAIL "输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/language/java_test.java b/os_smoke_src/testcases/develop/language/java_test.java new file mode 100755 index 0000000..b2e3692 --- /dev/null +++ b/os_smoke_src/testcases/develop/language/java_test.java @@ -0,0 +1,118 @@ +/* ############################################# */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2026 Tencent. All Rights Reserved. */ +/* Author: kevinzcheng */ +/* Date: 2026/03/11 */ +/* ############################################# */ +import java.util.*; +import java.util.stream.*; +import java.io.*; +import java.nio.file.*; + +public class java_test { + + static int errors = 0; + + static void check(boolean cond, String msg) { + if (!cond) { + System.err.println("FAIL: " + msg); + errors++; + } + } + + static void testCollections() { + List list = Arrays.asList(5, 3, 1, 4, 2); + List sorted = list.stream().sorted().collect(Collectors.toList()); + check(sorted.equals(Arrays.asList(1, 2, 3, 4, 5)), "stream sort"); + + Map map = new HashMap<>(); + map.put("a", 1); + map.put("b", 2); + map.put("c", 3); + check(map.size() == 3, "map size"); + check(map.get("b") == 2, "map get"); + + Set set = new HashSet<>(Arrays.asList("x", "y", "x")); + check(set.size() == 2, "set dedup"); + } + + static void testStreamsAndLambda() { + List words = Arrays.asList("hello", "world", "java", "test"); + String result = words.stream() + .filter(w -> w.length() == 5) + .map(String::toUpperCase) + .collect(Collectors.joining(" ")); + check(result.equals("HELLO WORLD"), "stream filter+map+join"); + + int sum = IntStream.rangeClosed(1, 10).sum(); + check(sum == 55, "IntStream sum"); + + Optional first = words.stream().findFirst(); + check(first.isPresent() && first.get().equals("hello"), "Optional"); + } + + static void testExceptionHandling() { + try { + throw new RuntimeException("test exception"); + } catch (RuntimeException e) { + check(e.getMessage().equals("test exception"), "exception message"); + } + + try { + int result = 10 / 0; + check(false, "ArithmeticException not thrown"); + } catch (ArithmeticException e) { + check(true, "ArithmeticException caught"); + } + } + + static void testFileIO() { + Path tmpFile = null; + try { + tmpFile = Files.createTempFile("java_smoke_", ".txt"); + String content = "java file io test"; + Files.write(tmpFile, content.getBytes()); + String read = new String(Files.readAllBytes(tmpFile)); + check(read.equals(content), "file read/write"); + } catch (IOException e) { + check(false, "file IO exception: " + e.getMessage()); + } finally { + if (tmpFile != null) { + try { Files.deleteIfExists(tmpFile); } catch (IOException ignored) {} + } + } + } + + static void testThreading() { + final int[] counter = {0}; + List threads = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + Thread t = new Thread(() -> { + synchronized (counter) { + counter[0]++; + } + }); + threads.add(t); + t.start(); + } + for (Thread t : threads) { + try { t.join(); } catch (InterruptedException ignored) {} + } + check(counter[0] == 5, "threading counter"); + } + + public static void main(String[] args) { + testCollections(); + testStreamsAndLambda(); + testExceptionHandling(); + testFileIO(); + testThreading(); + + if (errors == 0) { + System.out.println("hello world"); + } else { + System.err.println(errors + " Java tests failed"); + System.exit(1); + } + } +} diff --git a/os_smoke_src/testcases/develop/language/js_test.js b/os_smoke_src/testcases/develop/language/js_test.js new file mode 100755 index 0000000..c266d05 --- /dev/null +++ b/os_smoke_src/testcases/develop/language/js_test.js @@ -0,0 +1,111 @@ +// ############################################# +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2026 Tencent. All Rights Reserved. +// Author: kevinzcheng +// Date: 2026/03/11 +// ############################################# +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +let errors = 0; + +function check(cond, msg) { + if (!cond) { + console.error("FAIL: " + msg); + errors++; + } +} + +// Test 1: Promise and async/await +function testPromise() { + return new Promise((resolve, reject) => { + const result = 2 + 3; + if (result === 5) { + resolve(result); + } else { + reject(new Error("math failed")); + } + }); +} + +// Test 2: JSON operations +function testJSON() { + const obj = { name: "test", items: [1, 2, 3], nested: { key: "value" } }; + const jsonStr = JSON.stringify(obj); + const parsed = JSON.parse(jsonStr); + check(parsed.name === "test", "JSON name"); + check(Array.isArray(parsed.items) && parsed.items.length === 3, "JSON array"); + check(parsed.nested.key === "value", "JSON nested"); +} + +// Test 3: Array methods +function testArrayMethods() { + const nums = [5, 3, 1, 4, 2]; + const sorted = [...nums].sort((a, b) => a - b); + check(sorted.join(",") === "1,2,3,4,5", "Array sort"); + + const doubled = nums.map(n => n * 2); + check(doubled.reduce((a, b) => a + b, 0) === 30, "Array map+reduce"); + + const evens = nums.filter(n => n % 2 === 0); + check(evens.length === 2, "Array filter"); + + const found = nums.find(n => n > 3); + check(found === 5, "Array find"); +} + +// Test 4: File I/O with fs module +function testFileIO() { + const tmpFile = path.join(os.tmpdir(), "js_smoke_test.txt"); + const content = "nodejs file io test"; + fs.writeFileSync(tmpFile, content); + const read = fs.readFileSync(tmpFile, "utf-8"); + check(read === content, "fs read/write"); + fs.unlinkSync(tmpFile); +} + +// Test 5: String template literals and destructuring +function testModernSyntax() { + const [a, b, ...rest] = [1, 2, 3, 4, 5]; + check(a === 1 && b === 2 && rest.length === 3, "destructuring"); + + const name = "world"; + const greeting = `hello ${name}`; + check(greeting === "hello world", "template literal"); + + const { x, y = 10 } = { x: 1 }; + check(x === 1 && y === 10, "object destructuring with default"); +} + +// Test 6: Map and Set +function testMapSet() { + const map = new Map(); + map.set("one", 1); + map.set("two", 2); + check(map.size === 2, "Map size"); + check(map.get("one") === 1, "Map get"); + + const set = new Set([1, 2, 2, 3, 3]); + check(set.size === 3, "Set dedup"); +} + +// Run all tests +testPromise().then(function(result) { + check(result === 5, "Promise resolve"); +}).catch(function(e) { + check(false, "Promise failed: " + e.message); +}).then(function() { + testJSON(); + testArrayMethods(); + testFileIO(); + testModernSyntax(); + testMapSet(); + + if (errors === 0) { + console.log("hello world"); + } else { + console.error(errors + " Node.js tests failed"); + process.exit(1); + } +}); diff --git a/os_smoke_src/testcases/develop/language/nodejs_tc001.sh b/os_smoke_src/testcases/develop/language/nodejs_tc001.sh new file mode 100755 index 0000000..8cc079f --- /dev/null +++ b/os_smoke_src/testcases/develop/language/nodejs_tc001.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: nodejs_tc001 +# 描述: 验证 Node.js 运行时安装及执行功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: nodejs 可安装 +# 步骤: +# 1. 安装 nodejs 并检查 node 版本 +# 2. 执行测试脚本并验证输出 +# 预期结果: +# 1. node 版本正常 +# 2. 输出 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 Node.js 运行时安装及执行功能" +tcnt=2 + +SCRIPT_DIR="" + +setup() +{ + SCRIPT_DIR=$(dirname "$0") + ensure_command "node" "nodejs" +} + +cleanup() +{ + : +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 node 版本" + local ver + ver=$(node --version 2>&1) + if [ -n "$ver" ]; then + tst_res $TPASS "node 版本: $ver" + else + tst_res $TFAIL "无法获取 node 版本" + fi + ;; + 1) + tst_res $TINFO "执行 js_test.js" + local output + output=$(node "$SCRIPT_DIR/js_test.js" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "输出正确: $output" + else + tst_res $TFAIL "输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/language/perl_tc001.sh b/os_smoke_src/testcases/develop/language/perl_tc001.sh new file mode 100755 index 0000000..1970858 --- /dev/null +++ b/os_smoke_src/testcases/develop/language/perl_tc001.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: perl_tc001 +# 描述: 验证 Perl 解释器安装及执行功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: perl 已安装 +# 步骤: +# 1. 检查 perl 版本 +# 2. 执行测试脚本并验证输出 +# 预期结果: +# 1. perl 版本正常 +# 2. 输出 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC="验证 Perl 解释器安装及执行功能" +tcnt=2 + +SCRIPT_DIR="" + +setup() +{ + SCRIPT_DIR=$(dirname "$0") + ensure_command "perl" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 perl 版本" + local ver + ver=$(perl --version 2>&1 | grep "version" | head -1) + if [ -n "$ver" ]; then + tst_res $TPASS "perl 版本: $ver" + else + tst_res $TFAIL "无法获取 perl 版本" + fi + ;; + 1) + tst_res $TINFO "执行 pl_test.pl" + local output + output=$(perl "$SCRIPT_DIR/pl_test.pl" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "输出正确: $output" + else + tst_res $TFAIL "输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/language/pl_test.pl b/os_smoke_src/testcases/develop/language/pl_test.pl new file mode 100755 index 0000000..1be07bc --- /dev/null +++ b/os_smoke_src/testcases/develop/language/pl_test.pl @@ -0,0 +1,105 @@ +#!/usr/bin/perl +# ############################################# +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 +# ############################################# + +use strict; +use warnings; +use File::Temp qw(tempfile); + +my $errors = 0; + +sub check { + my ($cond, $msg) = @_; + unless ($cond) { + print STDERR "FAIL: $msg\n"; + $errors++; + } +} + +# Test 1: Regular expressions +sub test_regex { + my $str = "hello world 2026"; + check($str =~ /^hello\s+world/, "regex match"); + (my $replaced = $str) =~ s/\d+/YEAR/; + check($replaced eq "hello world YEAR", "regex substitute"); + my @nums = ("abc123def456" =~ /(\d+)/g); + check(scalar(@nums) == 2 && $nums[0] eq "123" && $nums[1] eq "456", "regex capture all"); +} + +# Test 2: Hash operations +sub test_hash { + my %h = (name => "test", age => 25, lang => "perl"); + check(scalar(keys %h) == 3, "hash size"); + check($h{name} eq "test", "hash access"); + check(exists $h{age}, "hash exists"); + delete $h{age}; + check(!exists $h{age}, "hash delete"); + + my @sorted_keys = sort keys %h; + check($sorted_keys[0] eq "lang" && $sorted_keys[1] eq "name", "hash sorted keys"); +} + +# Test 3: Array operations +sub test_array { + my @arr = (5, 3, 1, 4, 2); + my @sorted = sort { $a <=> $b } @arr; + check("@sorted" eq "1 2 3 4 5", "array sort"); + + my @mapped = map { $_ * 2 } @arr; + my $sum = 0; + $sum += $_ for @mapped; + check($sum == 30, "array map+sum"); + + my @filtered = grep { $_ > 3 } @arr; + check(scalar(@filtered) == 2, "array grep"); + + push @arr, 6; + check(scalar(@arr) == 6, "array push"); + my $popped = pop @arr; + check($popped == 6, "array pop"); +} + +# Test 4: File I/O +sub test_file_io { + my ($fh, $filename) = tempfile("perl_smoke_XXXX", TMPDIR => 1, UNLINK => 1); + print $fh "perl file io test\n"; + close $fh; + + open(my $rfh, '<', $filename) or do { + check(0, "file open: $!"); + return; + }; + my $content = <$rfh>; + chomp $content; + close $rfh; + check($content eq "perl file io test", "file read/write"); +} + +# Test 5: References and complex data structures +sub test_references { + my $aref = [1, 2, 3]; + check(ref($aref) eq 'ARRAY', "array ref type"); + check(scalar(@$aref) == 3, "array ref deref"); + + my $href = { a => 1, b => [2, 3] }; + check(ref($href) eq 'HASH', "hash ref type"); + check($href->{b}[1] == 3, "nested data structure"); +} + +# Run all tests +test_regex(); +test_hash(); +test_array(); +test_file_io(); +test_references(); + +if ($errors == 0) { + print("hello world\n"); +} else { + print STDERR "$errors Perl tests failed\n"; + exit(1); +} diff --git a/os_smoke_src/testcases/develop/language/py2_test.py b/os_smoke_src/testcases/develop/language/py2_test.py new file mode 100755 index 0000000..c4e0f18 --- /dev/null +++ b/os_smoke_src/testcases/develop/language/py2_test.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# ############################################# +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 +# ############################################# +import sys +import os +import json +import tempfile +import re + +errors = 0 + + +def check(cond, msg): + global errors + if not cond: + sys.stderr.write("FAIL: " + msg + "\n") + errors += 1 + + +def test_list_ops(): + squares = [x * x for x in range(5)] + check(squares == [0, 1, 4, 9, 16], "list comprehension") + check(sum(squares) == 30, "sum") + filtered = filter(lambda x: x > 5, squares) + check(list(filtered) == [9, 16], "filter") + + +def test_dict_ops(): + d = {"a": 1, "b": 2, "c": 3} + check(len(d) == 3, "dict len") + keys = sorted(d.keys()) + check(keys == ["a", "b", "c"], "sorted keys") + + +def test_string_ops(): + s = "Hello World" + check(s.lower() == "hello world", "lower") + check(s.split() == ["Hello", "World"], "split") + check(",".join(["a", "b", "c"]) == "a,b,c", "join") + + +def test_file_io(): + fd, tmpfile = tempfile.mkstemp(suffix=".txt") + try: + os.write(fd, b"python2 file test\n") + os.close(fd) + with open(tmpfile, "r") as f: + content = f.read().strip() + check(content == "python2 file test", "file read/write") + finally: + os.unlink(tmpfile) + + +def test_regex(): + m = re.match(r"(\w+)\s(\w+)", "hello world") + check(m is not None, "regex match") + check(m.group(1) == "hello", "regex group") + + +if __name__ == "__main__": + test_list_ops() + test_dict_ops() + test_string_ops() + test_file_io() + test_regex() + + if errors == 0: + print("hello world!") + else: + sys.stderr.write("%d Python2 tests failed\n" % errors) + sys.exit(1) diff --git a/os_smoke_src/testcases/develop/language/py3_test.py b/os_smoke_src/testcases/develop/language/py3_test.py new file mode 100755 index 0000000..19c175e --- /dev/null +++ b/os_smoke_src/testcases/develop/language/py3_test.py @@ -0,0 +1,157 @@ +#!/usr/bin/python3 +# ############################################# +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 +# ############################################# +import sys +import os +import json +import tempfile +import threading +import re +from collections import defaultdict + +errors = 0 + + +def check(cond, msg): + global errors + if not cond: + print("FAIL: " + msg, file=sys.stderr) + errors += 1 + + +def test_list_comprehension(): + squares = [x * x for x in range(10)] + check(squares == [0, 1, 4, 9, 16, 25, 36, 49, 64, 81], "list comprehension") + + evens = [x for x in range(20) if x % 2 == 0] + check(len(evens) == 10 and evens[-1] == 18, "filtered list comp") + + matrix = [[i * 3 + j for j in range(3)] for i in range(3)] + check(matrix[2][2] == 8, "nested list comp") + + gen_sum = sum(x for x in range(101)) + check(gen_sum == 5050, "generator expression") + + +def test_dict_operations(): + d = {"a": 1, "b": 2, "c": 3} + check(len(d) == 3, "dict len") + + merged = {**d, "d": 4, "a": 10} + check(merged["a"] == 10 and merged["d"] == 4, "dict merge") + + dd = defaultdict(list) + dd["fruits"].append("apple") + dd["fruits"].append("banana") + check(len(dd["fruits"]) == 2, "defaultdict") + + comp = {k: v * 2 for k, v in d.items()} + check(comp == {"a": 2, "b": 4, "c": 6}, "dict comprehension") + + +def test_exception_handling(): + try: + raise ValueError("test error") + except ValueError as e: + check(str(e) == "test error", "ValueError catch") + + try: + _ = 1 / 0 + check(False, "ZeroDivisionError not raised") + except ZeroDivisionError: + check(True, "ZeroDivisionError caught") + + class CustomError(Exception): + def __init__(self, code, msg): + self.code = code + super().__init__(msg) + + try: + raise CustomError(404, "not found") + except CustomError as e: + check(e.code == 404 and str(e) == "not found", "custom exception") + + +def test_file_io(): + with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f: + tmpfile = f.name + f.write("python file io test\n") + try: + with open(tmpfile, "r") as f: + content = f.read().strip() + check(content == "python file io test", "file read/write") + finally: + os.unlink(tmpfile) + + +def test_json(): + data = {"name": "test", "items": [1, 2, 3], "nested": {"key": "value"}} + s = json.dumps(data, sort_keys=True) + parsed = json.loads(s) + check(parsed["name"] == "test", "json name") + check(parsed["items"] == [1, 2, 3], "json list") + check(parsed["nested"]["key"] == "value", "json nested") + + +def test_threading(): + results = [] + lock = threading.Lock() + + def worker(n): + with lock: + results.append(n * n) + + threads = [threading.Thread(target=worker, args=(i,)) for i in range(5)] + for t in threads: + t.start() + for t in threads: + t.join() + + check(sorted(results) == [0, 1, 4, 9, 16], "threading results") + + +def test_regex(): + m = re.match(r"(\d{4})-(\d{2})-(\d{2})", "2026-03-11") + check(m is not None, "regex match") + check(m.group(1) == "2026", "regex group") + + found = re.findall(r"\b\w+\b", "hello world test") + check(found == ["hello", "world", "test"], "regex findall") + + +def test_class_and_inheritance(): + class Animal: + def __init__(self, name): + self.name = name + + def speak(self): + raise NotImplementedError + + class Dog(Animal): + def speak(self): + return f"{self.name} says woof" + + d = Dog("Rex") + check(isinstance(d, Animal), "isinstance") + check(d.speak() == "Rex says woof", "inheritance method") + + +if __name__ == "__main__": + test_list_comprehension() + test_dict_operations() + test_exception_handling() + test_file_io() + test_json() + test_threading() + test_regex() + test_class_and_inheritance() + + if errors == 0: + print("hello world!") + else: + print(f"{errors} Python3 tests failed", file=sys.stderr) + sys.exit(1) diff --git a/os_smoke_src/testcases/develop/language/python_tc001.sh b/os_smoke_src/testcases/develop/language/python_tc001.sh new file mode 100755 index 0000000..b92f5d0 --- /dev/null +++ b/os_smoke_src/testcases/develop/language/python_tc001.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: python_tc001 +# 描述: 验证 Python3 解释器执行功能,可选验证 Python2 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: python3 已安装 +# 步骤: +# 1. 检查 python3 版本 +# 2. 执行 py3_test.py 并验证输出(若 python2 存在则额外验证 py2_test.py) +# 预期结果: +# 1. python3 版本正常 +# 2. 输出 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 Python 解释器执行功能" +tcnt=2 + +SCRIPT_DIR="" + +setup() +{ + SCRIPT_DIR=$(dirname "$0") + ensure_command "python3" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 python3 版本" + local ver + ver=$(python3 --version 2>&1) + if [ -n "$ver" ]; then + tst_res $TPASS "python3 版本: $ver" + else + tst_res $TFAIL "无法获取 python3 版本" + fi + ;; + 1) + tst_res $TINFO "执行 py3_test.py" + local output + output=$(python3 "$SCRIPT_DIR/py3_test.py" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "python3 输出正确: $output" + else + tst_res $TFAIL "python3 输出异常: $output" + fi + + if which python2 >/dev/null 2>&1; then + tst_res $TINFO "检测到 python2,额外验证" + output=$(python2 "$SCRIPT_DIR/py2_test.py" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "python2 输出正确: $output" + else + tst_res $TWARN "python2 输出异常: $output" + fi + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/language/rb_test.rb b/os_smoke_src/testcases/develop/language/rb_test.rb new file mode 100755 index 0000000..b7c9469 --- /dev/null +++ b/os_smoke_src/testcases/develop/language/rb_test.rb @@ -0,0 +1,125 @@ +# ############################################# +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 +# ############################################# +require 'json' +require 'tempfile' + +$errors = 0 + +def check(cond, msg) + unless cond + $stderr.puts "FAIL: #{msg}" + $errors += 1 + end +end + +# Test 1: Array operations and iterators +def test_array + arr = [5, 3, 1, 4, 2] + check(arr.sort == [1, 2, 3, 4, 5], "array sort") + check(arr.map { |x| x * 2 }.reduce(:+) == 30, "map+reduce") + check(arr.select { |x| x > 3 } == [5, 4], "select") + check(arr.reject { |x| x > 3 } == [3, 1, 2], "reject") + check(arr.min == 1 && arr.max == 5, "min/max") + check(arr.include?(3), "include?") +end + +# Test 2: Hash operations +def test_hash + h = { name: "test", age: 25, lang: "ruby" } + check(h.size == 3, "hash size") + check(h[:name] == "test", "hash access") + check(h.key?(:age), "hash key?") + check(h.values.include?(25), "hash values") + + merged = h.merge({ age: 30, version: "3.0" }) + check(merged[:age] == 30 && merged[:version] == "3.0", "hash merge") +end + +# Test 3: String and regex +def test_string_regex + s = "Hello World 2026" + check(s.downcase == "hello world 2026", "downcase") + check(s.split(" ").length == 3, "split") + check(s.match?(/\d{4}/), "regex match") + check(s.gsub(/\d+/, "YEAR") == "Hello World YEAR", "gsub") + check(s.scan(/\w+/) == ["Hello", "World", "2026"], "scan") +end + +# Test 4: File I/O +def test_file_io + tmpfile = Tempfile.new("ruby_smoke") + begin + tmpfile.write("ruby file io test\n") + tmpfile.rewind + content = tmpfile.read.strip + check(content == "ruby file io test", "file read/write") + ensure + tmpfile.close + tmpfile.unlink + end +end + +# Test 5: Exception handling +def test_exception + begin + raise RuntimeError, "test error" + rescue RuntimeError => e + check(e.message == "test error", "exception message") + end + + begin + 1 / 0 + check(false, "ZeroDivisionError not raised") + rescue ZeroDivisionError + check(true, "ZeroDivisionError caught") + end +end + +# Test 6: Blocks and Procs +def test_blocks + result = [1, 2, 3].each_with_object([]) { |x, acc| acc << x * x } + check(result == [1, 4, 9], "each_with_object") + + doubler = Proc.new { |x| x * 2 } + check(doubler.call(5) == 10, "Proc") + + adder = lambda { |a, b| a + b } + check(adder.call(3, 4) == 7, "lambda") +end + +# Test 7: Class and inheritance +def test_class + klass = Class.new do + attr_accessor :name + def initialize(name) + @name = name + end + def greet + "Hi, #{@name}" + end + end + + obj = klass.new("Ruby") + check(obj.name == "Ruby", "attr_accessor") + check(obj.greet == "Hi, Ruby", "instance method") +end + +# Run all tests +test_array +test_hash +test_string_regex +test_file_io +test_exception +test_blocks +test_class + +if $errors == 0 + puts "hello world" +else + $stderr.puts "#{$errors} Ruby tests failed" + exit(1) +end diff --git a/os_smoke_src/testcases/develop/language/rs_test.rs b/os_smoke_src/testcases/develop/language/rs_test.rs new file mode 100755 index 0000000..733ca53 --- /dev/null +++ b/os_smoke_src/testcases/develop/language/rs_test.rs @@ -0,0 +1,169 @@ +// ############################################# +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2026 Tencent. All Rights Reserved. +// Author: kevinzcheng +// Date: 2026/03/11 +// ############################################# +use std::collections::HashMap; +use std::fs; +use std::io::Write; + +static mut ERRORS: i32 = 0; + +fn check(cond: bool, msg: &str) { + if !cond { + eprintln!("FAIL: {}", msg); + unsafe { ERRORS += 1; } + } +} + +// Test 1: Ownership and borrowing +fn test_ownership() { + let s1 = String::from("hello"); + let s2 = s1.clone(); + check(s1 == "hello" && s2 == "hello", "clone ownership"); + + let s3 = &s2; + check(s3.len() == 5, "borrow reference"); + + let mut s4 = String::from("hello"); + s4.push_str(" world"); + check(s4 == "hello world", "mutable string"); +} + +// Test 2: Result and Option +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + Err("division by zero".to_string()) + } else { + Ok(a / b) + } +} + +fn test_result_option() { + match divide(10.0, 3.0) { + Ok(v) => check((v - 3.333).abs() < 0.01, "Result Ok"), + Err(_) => check(false, "unexpected Err"), + } + + match divide(10.0, 0.0) { + Ok(_) => check(false, "expected Err"), + Err(e) => check(e == "division by zero", "Result Err"), + } + + let some_val: Option = Some(42); + check(some_val.unwrap() == 42, "Option Some"); + let none_val: Option = None; + check(none_val.unwrap_or(0) == 0, "Option None unwrap_or"); +} + +// Test 3: Structs and traits +trait Describable { + fn describe(&self) -> String; +} + +struct Point { + x: f64, + y: f64, +} + +impl Describable for Point { + fn describe(&self) -> String { + format!("Point({}, {})", self.x, self.y) + } +} + +impl Point { + fn distance(&self, other: &Point) -> f64 { + ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt() + } +} + +fn test_struct_trait() { + let p1 = Point { x: 0.0, y: 0.0 }; + let p2 = Point { x: 3.0, y: 4.0 }; + check((p1.distance(&p2) - 5.0).abs() < 0.001, "struct method"); + check(p1.describe() == "Point(0, 0)", "trait impl"); +} + +// Test 4: Collections +fn test_collections() { + let mut v = vec![5, 3, 1, 4, 2]; + v.sort(); + check(v == vec![1, 2, 3, 4, 5], "Vec sort"); + + let sum: i32 = v.iter().sum(); + check(sum == 15, "iter sum"); + + let doubled: Vec = v.iter().map(|x| x * 2).collect(); + check(doubled == vec![2, 4, 6, 8, 10], "iter map"); + + let evens: Vec<&i32> = v.iter().filter(|x| *x % 2 == 0).collect(); + check(evens.len() == 2, "iter filter"); + + let mut map = HashMap::new(); + map.insert("a", 1); + map.insert("b", 2); + check(map.len() == 2, "HashMap len"); + check(*map.get("a").unwrap() == 1, "HashMap get"); +} + +// Test 5: File I/O +fn test_file_io() { + let path = std::env::temp_dir().join("rust_smoke_test.txt"); + let content = "rust file io test"; + { + let mut file = fs::File::create(&path).expect("create file"); + file.write_all(content.as_bytes()).expect("write file"); + } + let read = fs::read_to_string(&path).expect("read file"); + check(read == content, "file read/write"); + fs::remove_file(&path).expect("remove file"); +} + +// Test 6: Closures and iterators +fn test_closures() { + let add = |a: i32, b: i32| -> i32 { a + b }; + check(add(2, 3) == 5, "closure"); + + let words = vec!["hello", "world"]; + let result: String = words.join(" "); + check(result == "hello world", "join"); + + let nums: Vec = (1..=5).collect(); + check(nums == vec![1, 2, 3, 4, 5], "range collect"); +} + +// Test 7: Pattern matching +fn test_pattern_match() { + enum Shape { + Circle(f64), + Rectangle(f64, f64), + } + + let shapes = vec![Shape::Circle(5.0), Shape::Rectangle(3.0, 4.0)]; + for s in &shapes { + match s { + Shape::Circle(r) => check(*r == 5.0, "enum Circle match"), + Shape::Rectangle(w, h) => check(*w == 3.0 && *h == 4.0, "enum Rect match"), + } + } +} + +fn main() { + test_ownership(); + test_result_option(); + test_struct_trait(); + test_collections(); + test_file_io(); + test_closures(); + test_pattern_match(); + + let err_count = unsafe { ERRORS }; + if err_count == 0 { + println!("hello world"); + } else { + eprintln!("{} Rust tests failed", err_count); + std::process::exit(1); + } +} diff --git a/os_smoke_src/testcases/develop/language/ruby_tc001.sh b/os_smoke_src/testcases/develop/language/ruby_tc001.sh new file mode 100755 index 0000000..2ab7d5a --- /dev/null +++ b/os_smoke_src/testcases/develop/language/ruby_tc001.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ruby_tc001 +# 描述: 验证 Ruby 解释器安装及执行功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: ruby 可安装 +# 步骤: +# 1. 安装 ruby 并检查版本 +# 2. 执行测试脚本并验证输出 +# 预期结果: +# 1. ruby 版本正常 +# 2. 输出 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 Ruby 解释器安装及执行功能" +tcnt=2 + +SCRIPT_DIR="" + +setup() +{ + SCRIPT_DIR=$(dirname "$0") + ensure_command "ruby" +} + +cleanup() +{ + : +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 ruby 版本" + local ver + ver=$(ruby --version 2>&1) + if echo "$ver" | grep -q "ruby"; then + tst_res $TPASS "ruby 版本: $ver" + else + tst_res $TFAIL "无法获取 ruby 版本" + fi + ;; + 1) + tst_res $TINFO "执行 rb_test.rb" + local output + output=$(ruby "$SCRIPT_DIR/rb_test.rb" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "输出正确: $output" + else + tst_res $TFAIL "输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/develop/language/rust_tc001.sh b/os_smoke_src/testcases/develop/language/rust_tc001.sh new file mode 100755 index 0000000..92d4d97 --- /dev/null +++ b/os_smoke_src/testcases/develop/language/rust_tc001.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: rust_tc001 +# 描述: 验证 Rust 编译器安装及编译执行功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: rust/rustc 可安装 +# 步骤: +# 1. 安装 rust 并检查 rustc 版本 +# 2. rustc 编译测试程序 +# 3. 执行并验证输出 +# 预期结果: +# 1. rustc 版本正常 +# 2. 编译成功 +# 3. 输出 hello world +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC="验证 Rust 编译器安装及编译执行功能" +tcnt=3 + +WORK_DIR="" +SCRIPT_DIR="" + +setup() +{ + SCRIPT_DIR=$(dirname "$0") + WORK_DIR=$(mktemp -d) + cp "$SCRIPT_DIR/rs_test.rs" "$WORK_DIR/" + + ensure_command "rustc" "rust" +} + +cleanup() +{ + [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" +} + +run_test() +{ + case $1 in + 0) + tst_res $TINFO "检查 rustc 版本" + local ver + ver=$(rustc --version 2>&1) + if echo "$ver" | grep -q "rustc"; then + tst_res $TPASS "rustc 版本: $ver" + else + tst_res $TFAIL "无法获取 rustc 版本" + fi + ;; + 1) + tst_res $TINFO "rustc 编译 rs_test.rs" + rustc "$WORK_DIR/rs_test.rs" -o "$WORK_DIR/rs_test" 2>&1 + if [ $? -eq 0 ] && [ -x "$WORK_DIR/rs_test" ]; then + tst_res $TPASS "rustc 编译成功" + else + tst_res $TFAIL "rustc 编译失败" + fi + ;; + 2) + tst_res $TINFO "执行编译产物并验证输出" + local output + output=$("$WORK_DIR/rs_test" 2>&1) + if echo "$output" | grep -qi "hello world"; then + tst_res $TPASS "输出正确: $output" + else + tst_res $TFAIL "输出异常: $output" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/driver/Makefile b/os_smoke_src/testcases/driver/Makefile new file mode 100755 index 0000000..12e8c3b --- /dev/null +++ b/os_smoke_src/testcases/driver/Makefile @@ -0,0 +1,22 @@ +subdir := $(shell find . -maxdepth 1 -type d) +dirs := $(filter-out .,$(subdir)) +dirs := $(basename $(patsubst ./%,%,$(dirs))) + +.PHONY: $(dirs) + +$(dirs): + echo $(dirs) + @for dir in $(dirs); do \ + $(MAKE) -C $$dir || exit "$$?"; \ + done + +install: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir install || exit "$$?"; \ + done + +clean: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir clean || exit "$$?"; \ + done + diff --git a/os_smoke_src/testcases/kernel/Makefile b/os_smoke_src/testcases/kernel/Makefile new file mode 100755 index 0000000..12e8c3b --- /dev/null +++ b/os_smoke_src/testcases/kernel/Makefile @@ -0,0 +1,22 @@ +subdir := $(shell find . -maxdepth 1 -type d) +dirs := $(filter-out .,$(subdir)) +dirs := $(basename $(patsubst ./%,%,$(dirs))) + +.PHONY: $(dirs) + +$(dirs): + echo $(dirs) + @for dir in $(dirs); do \ + $(MAKE) -C $$dir || exit "$$?"; \ + done + +install: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir install || exit "$$?"; \ + done + +clean: + @for dir in $(dirs); do \ + $(MAKE) -C $$dir clean || exit "$$?"; \ + done + diff --git a/os_smoke_src/testcases/kernel/cpu/Makefile b/os_smoke_src/testcases/kernel/cpu/Makefile new file mode 100755 index 0000000..90d2e63 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/Makefile @@ -0,0 +1,90 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_CFG := $(wildcard *.cfg) +CFLAGS += -g +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 检测openssl是否支持SM4(低版本无EVP_sm4_ecb) +HAS_SM4 := $(shell printf '\#include \nint main(){EVP_sm4_ecb();return 0;}' | $(CC) -xc - -o /dev/null -lssl -lcrypto 2>/dev/null && echo yes || echo no) + +# 检测是否为x86_64架构(hw_rand_tc001使用rdrand指令,仅x86支持) +IS_X86_64 := $(shell uname -m | grep -q x86_64 && echo yes || echo no) + +# 将架构/依赖相关的用例从通用列表中分离 +EXCLUDE_SOURCE := soft_cryp_tc001.c hw_rand_tc001.c +COMMON_SOURCE := $(filter-out $(EXCLUDE_SOURCE),$(CASES_SOURCE)) +COMMON_OBJ := $(patsubst %.c,%.o,$(COMMON_SOURCE)) +COMMON_BIN := $(patsubst %.o,%,$(COMMON_OBJ)) + +COMMON_LDFLAGS := -lpthread -lnuma + +# 只有存在通用.c文件时才编译 +ifneq ($(COMMON_SOURCE),) +all: $(COMMON_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# soft_cryp_tc001单独处理:仅在SM4可用时编译 +ifeq ($(HAS_SM4),yes) +all: soft_cryp_tc001 +soft_cryp_tc001: soft_cryp_tc001.o + $(CC) $^ -o $@ -lssl -lcrypto +soft_cryp_tc001.o: soft_cryp_tc001.c + $(CC) $(CFLAGS) -c $^ -o $@ +else +all: + @echo "OpenSSL SM4 not supported on this system, skipping soft_cryp_tc001" +endif + +# hw_rand_tc001单独处理:仅在x86_64架构上编译(rdrand为x86专有指令) +ifeq ($(IS_X86_64),yes) +all: hw_rand_tc001 +hw_rand_tc001: hw_rand_tc001.o + $(CC) $^ -o $@ $(COMMON_LDFLAGS) +hw_rand_tc001.o: hw_rand_tc001.c + $(CC) $(CFLAGS) -c $^ -o $@ +else +all: + @echo "Non-x86_64 architecture, skipping hw_rand_tc001 (rdrand not supported)" +endif + +# 通用编译规则 +ifneq ($(COMMON_SOURCE),) +$(COMMON_BIN):%:%.o + $(CC) $^ -o $@ $(COMMON_LDFLAGS) + +$(COMMON_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) +ifneq ($(COMMON_BIN),) + cp -avf $(COMMON_BIN) $(INSTALL_DIR) +endif +ifeq ($(HAS_SM4),yes) + cp -avf soft_cryp_tc001 $(INSTALL_DIR) +endif +ifeq ($(IS_X86_64),yes) + cp -avf hw_rand_tc001 $(INSTALL_DIR) +endif +ifneq ($(CASES_SH),) + cp -avf $(CASES_SH) $(INSTALL_DIR) +endif +ifneq ($(CASES_PY),) + cp -avf $(CASES_PY) $(INSTALL_DIR) +endif +ifneq ($(CASES_CFG),) + cp -avf $(CASES_CFG) $(INSTALL_DIR) +endif + +clean: +ifneq ($(COMMON_SOURCE),) + rm -rfv $(COMMON_OBJ) $(COMMON_BIN) +endif + rm -f soft_cryp_tc001.o soft_cryp_tc001 + rm -f hw_rand_tc001.o hw_rand_tc001 + diff --git a/os_smoke_src/testcases/kernel/cpu/cpu_cgroup_tc001.sh b/os_smoke_src/testcases/kernel/cpu/cpu_cgroup_tc001.sh new file mode 100755 index 0000000..3f96302 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/cpu_cgroup_tc001.sh @@ -0,0 +1,153 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cpu_cgroup_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 验证CPU cgroup资源限制功能,通过cgroup限制进程的CPU使用率 +# 前置条件: 内核支持cgroup,已挂载cpu子系统 +# 步骤: +# 1. 创建cpu cgroup,设置cpu.cfs_quota_us限制为单核50% +# 2. 将负载进程加入该cgroup +# 3. 检查进程CPU使用率是否被限制在约50% +# 预期结果: +# 1. cgroup创建和配置成功 +# 2. 进程成功加入cgroup +# 3. CPU使用率被限制在50%左右(允许±15%误差) +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "cgroup cpu限额配置验证" + "cgroup cpu使用率限制验证" +) +tcnt=${#TCASE_DESC[@]} + +CGROUP_PATH="" +CGROUP_V2=0 +STRESS_PID="" + +setup() +{ + killall stress-ng 2>/dev/null || true + + # 检测cgroup版本 + if mount | grep -q "cgroup2"; then + CGROUP_V2=1 + local cg2_mount=$(mount | grep "cgroup2" | head -1 | awk '{print $3}') + CGROUP_PATH="${cg2_mount}/cpu_cgroup_tc001_test" + mkdir -p "$CGROUP_PATH" + # cgroupv2: 启用cpu控制器 + local parent_dir=$(dirname "$CGROUP_PATH") + echo "+cpu" > "${parent_dir}/cgroup.subtree_control" 2>/dev/null + elif [ -d "/sys/fs/cgroup/cpu" ]; then + CGROUP_PATH="/sys/fs/cgroup/cpu/cpu_cgroup_tc001_test" + mkdir -p "$CGROUP_PATH" + else + tst_brk $TCONF "cpu cgroup not available" + fi +} + +cleanup() +{ + killall stress-ng 2>/dev/null || true + sleep 1 + if [ -n "$CGROUP_PATH" ] && [ -d "$CGROUP_PATH" ]; then + # 移出所有进程 + if [ "$CGROUP_V2" -eq 1 ]; then + local parent_dir=$(dirname "$CGROUP_PATH") + for pid in $(cat "$CGROUP_PATH/cgroup.procs" 2>/dev/null); do + echo $pid > "${parent_dir}/cgroup.procs" 2>/dev/null + done + else + for pid in $(cat "$CGROUP_PATH/tasks" 2>/dev/null); do + echo $pid > /sys/fs/cgroup/cpu/tasks 2>/dev/null + done + fi + rmdir "$CGROUP_PATH" 2>/dev/null + fi + rm -rf $0.log* 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local quota_file period_file + if [ "$CGROUP_V2" -eq 1 ]; then + # cgroupv2: cpu.max 格式 "quota period" + echo "50000 100000" > "$CGROUP_PATH/cpu.max" + local result=$(cat "$CGROUP_PATH/cpu.max") + if echo "$result" | grep -q "50000"; then + tst_res $TPASS "cgroupv2 cpu.max set to 50000 100000" + else + tst_res $TFAIL "cgroupv2 cpu.max setting failed: $result" + fi + else + echo 100000 > "$CGROUP_PATH/cpu.cfs_period_us" + echo 50000 > "$CGROUP_PATH/cpu.cfs_quota_us" + local quota=$(cat "$CGROUP_PATH/cpu.cfs_quota_us") + if [ "$quota" -eq 50000 ]; then + tst_res $TPASS "cgroup cpu quota set to 50000 (50%)" + else + tst_res $TFAIL "cgroup cpu quota setting failed: $quota" + fi + fi + ;; + 1) + # 先将当前shell加入cgroup,这样fork出的stress-ng及其worker自动继承 + if [ "$CGROUP_V2" -eq 1 ]; then + echo $$ > "$CGROUP_PATH/cgroup.procs" + else + echo $$ > "$CGROUP_PATH/tasks" + fi + + stress-ng --cpu 1 --cpu-load 100 & + STRESS_PID=$! + sleep 10 + + # 将当前shell移回根cgroup,避免影响后续操作 + if [ "$CGROUP_V2" -eq 1 ]; then + local parent_dir=$(dirname "$CGROUP_PATH") + echo $$ > "${parent_dir}/cgroup.procs" + else + echo $$ > /sys/fs/cgroup/cpu/tasks + fi + + # 查询cgroup内所有进程的CPU使用率之和 + local pids + if [ "$CGROUP_V2" -eq 1 ]; then + pids=$(cat "$CGROUP_PATH/cgroup.procs" 2>/dev/null | tr '\n' ',') + else + pids=$(cat "$CGROUP_PATH/tasks" 2>/dev/null | tr '\n' ',') + fi + pids=${pids%,} + + local cpu_usage=0 + if [ -n "$pids" ]; then + cpu_usage=$(ps -p "$pids" -o %cpu= 2>/dev/null | awk '{s+=$1} END {printf "%d", s}') + fi + + if [ -z "$cpu_usage" ]; then + tst_res $TFAIL "failed to get CPU usage for cgroup processes" + return + fi + + tst_res $TINFO "measured CPU usage: ${cpu_usage}% (limit: 50%)" + if [ "$cpu_usage" -ge 35 ] && [ "$cpu_usage" -le 65 ]; then + tst_res $TPASS "CPU usage ${cpu_usage}% within expected range (35%-65%)" + else + tst_res $TFAIL "CPU usage ${cpu_usage}% out of expected range (35%-65%)" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/cpu/cpu_hotplug_tc001.sh b/os_smoke_src/testcases/kernel/cpu/cpu_hotplug_tc001.sh new file mode 100755 index 0000000..07d5fe9 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/cpu_hotplug_tc001.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cpu_hotplug_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 验证CPU热插拔功能,对非boot CPU执行offline/online操作 +# 前置条件: 系统CPU数量大于1 +# 步骤: +# 1. 获取可热插拔的CPU列表(排除CPU0) +# 2. 逐个offline目标CPU,验证状态变为0 +# 3. 逐个online目标CPU,验证状态恢复为1 +# 预期结果: +# 1. 获取到可热插拔CPU列表 +# 2. offline操作成功,CPU状态为0 +# 3. online操作成功,CPU状态恢复为1 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "CPU offline验证" + "CPU online恢复验证" +) +tcnt=${#TCASE_DESC[@]} + +TARGET_CPUS="" + +setup() +{ + local cpu_num=$(cat /proc/cpuinfo | grep processor | wc -l) + if [ "$cpu_num" -le 1 ]; then + tst_brk $TCONF "only 1 CPU, hotplug test requires at least 2 CPUs" + fi + + if is_virtual_machine; then + tst_brk $TCONF "虚拟机环境不支持CPU热插拔测试" + fi + + # 选取最后一个CPU作为测试目标 + TARGET_CPUS=$((cpu_num - 1)) + + local online=$(cat /sys/devices/system/cpu/cpu${TARGET_CPUS}/online 2>/dev/null) + if [ -z "$online" ]; then + tst_brk $TCONF "cpu${TARGET_CPUS} does not support hotplug" + fi +} + +cleanup() +{ + # 确保目标CPU恢复online + if [ -n "$TARGET_CPUS" ]; then + echo 1 > /sys/devices/system/cpu/cpu${TARGET_CPUS}/online 2>/dev/null + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + echo 0 > /sys/devices/system/cpu/cpu${TARGET_CPUS}/online + sleep 2 + local state=$(cat /sys/devices/system/cpu/cpu${TARGET_CPUS}/online) + if [ "$state" -eq 0 ]; then + tst_res $TPASS "cpu${TARGET_CPUS} offline succeeded, state=$state" + else + tst_res $TFAIL "cpu${TARGET_CPUS} offline failed, state=$state" + fi + ;; + 1) + echo 1 > /sys/devices/system/cpu/cpu${TARGET_CPUS}/online + sleep 2 + local state=$(cat /sys/devices/system/cpu/cpu${TARGET_CPUS}/online) + if [ "$state" -eq 1 ]; then + tst_res $TPASS "cpu${TARGET_CPUS} online restored, state=$state" + else + tst_res $TFAIL "cpu${TARGET_CPUS} online restore failed, state=$state" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/cpu/cpu_idle_tc001.sh b/os_smoke_src/testcases/kernel/cpu/cpu_idle_tc001.sh new file mode 100755 index 0000000..7026cf7 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/cpu_idle_tc001.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cpu_idle_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: 验证CPU idle状态信息可读取,且空闲时idle时间在增长 +# 前置条件: NA +# 步骤: +# 1. 读取/proc/stat中各CPU的idle值,等待5秒后再次读取 +# 2. 验证idle值有增长(说明CPU进入了idle状态) +# 3. 检查cpuidle sysfs接口是否存在且可读 +# 预期结果: +# 1. idle值成功读取 +# 2. 空闲状态下idle时间增长 +# 3. cpuidle sysfs可读 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "CPU idle时间增长验证" + "cpuidle sysfs接口检查" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + return 0 +} + +cleanup() +{ + rm -rf $0.log* 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # 读取cpu0的idle值 (第5列) + local idle1=$(grep "^cpu0 " /proc/stat | awk '{print $5}') + sleep 5 + local idle2=$(grep "^cpu0 " /proc/stat | awk '{print $5}') + + if [ -z "$idle1" ] || [ -z "$idle2" ]; then + tst_res $TFAIL "failed to read CPU idle from /proc/stat" + return + fi + + local diff=$((idle2 - idle1)) + tst_res $TINFO "cpu0 idle: before=$idle1 after=$idle2 diff=$diff" + if [ "$diff" -gt 0 ]; then + tst_res $TPASS "CPU idle time increased by $diff ticks" + else + tst_res $TFAIL "CPU idle time did not increase (diff=$diff)" + fi + ;; + 1) + local cpuidle_dir="/sys/devices/system/cpu/cpu0/cpuidle" + if [ ! -d "$cpuidle_dir" ]; then + tst_res $TCONF "cpuidle sysfs not available (may be disabled or unsupported)" + return + fi + + local state_dirs=$(find "$cpuidle_dir" -maxdepth 1 -type d -name "state*" | sort) + if [ -z "$state_dirs" ]; then + tst_res $TCONF "no cpuidle states found" + return + fi + + local fail=0 + for state_dir in $state_dirs; do + local state_name=$(basename "$state_dir") + for f in name desc latency usage time; do + if [ ! -r "$state_dir/$f" ]; then + tst_res $TFAIL "$state_name/$f not readable" + fail=1 + break 2 + fi + done + done + [ $fail -eq 0 ] && tst_res $TPASS "cpuidle sysfs all state entries readable" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/cpu/cpu_info_tc001.sh b/os_smoke_src/testcases/kernel/cpu/cpu_info_tc001.sh new file mode 100755 index 0000000..9a0e2de --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/cpu_info_tc001.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cpu_info_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 验证CPU信息获取接口,检查/proc/cpuinfo和lscpu输出的一致性和完整性 +# 前置条件: NA +# 步骤: +# 1. 读取/proc/cpuinfo,检查processor、model name等关键字段存在且非空 +# 2. 执行lscpu,检查Architecture、CPU(s)、Model name等关键字段 +# 3. 对比/proc/cpuinfo中的processor数量与lscpu报告的CPU(s)数量是否一致 +# 预期结果: +# 1. /proc/cpuinfo关键字段完整且非空 +# 2. lscpu关键字段完整且非空 +# 3. 两者报告的CPU数量一致 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "/proc/cpuinfo关键字段检查" + "lscpu关键字段检查" + "CPU数量一致性校验" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + if ! command -v lscpu > /dev/null 2>&1; then + tst_brk $TCONF "lscpu command not found" + fi +} + +cleanup() +{ + rm -rf $0.log* 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local fail=0 + local arch=$(uname -m) + local fields="" + case "$arch" in + x86_64|i*86) + fields="processor model_name cpu_MHz bogomips" + ;; + aarch64|arm*) + fields="processor BogoMIPS CPU_implementer CPU_architecture" + ;; + *) + fields="processor" + ;; + esac + for raw_field in $fields; do + local field=$(echo "$raw_field" | tr '_' ' ') + local count=$(grep -ic "$field" /proc/cpuinfo) + if [ "$count" -eq 0 ]; then + tst_res $TFAIL "/proc/cpuinfo missing field: $field (arch=$arch)" + fail=1 + break + fi + done + [ $fail -eq 0 ] && tst_res $TPASS "/proc/cpuinfo all key fields present (arch=$arch)" + ;; + 1) + lscpu > $0.log1 2>&1 + local fail=0 + for field in "Architecture" "CPU(s)" "Model name" "Socket(s)"; do + if ! grep -q "$field" $0.log1; then + tst_res $TFAIL "lscpu missing field: $field" + fail=1 + break + fi + done + [ $fail -eq 0 ] && tst_res $TPASS "lscpu all key fields present" + ;; + 2) + local cpuinfo_num=$(grep -c "^processor" /proc/cpuinfo) + local lscpu_num=$(lscpu | grep "^CPU(s):" | awk '{print $2}') + if [ "$cpuinfo_num" -eq "$lscpu_num" ]; then + tst_res $TPASS "CPU count consistent: cpuinfo=$cpuinfo_num lscpu=$lscpu_num" + else + tst_res $TFAIL "CPU count mismatch: cpuinfo=$cpuinfo_num lscpu=$lscpu_num" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/cpu/cpu_isolate_tc001.sh b/os_smoke_src/testcases/kernel/cpu/cpu_isolate_tc001.sh new file mode 100755 index 0000000..0b2a957 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/cpu_isolate_tc001.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cpu_isolate_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: 验证CPU隔离功能,通过cset/taskset将进程隔离到指定CPU,确认其他进程不会被调度到该CPU +# 前置条件: yum install -y util-linux stress-ng procps-ng +# 步骤: +# 1. 选择一个非boot CPU,启动负载进程绑定到该CPU +# 2. 启动其他负载进程,检查它们是否未运行在隔离CPU上 +# 3. 验证隔离CPU上仅运行绑定的进程 +# 预期结果: +# 1. 绑定进程成功运行在目标CPU上 +# 2. 其他负载进程不运行在隔离CPU上 +# 3. 隔离CPU上仅有绑定的进程 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "CPU隔离绑定验证" + "隔离CPU排他性验证" +) +tcnt=${#TCASE_DESC[@]} + +ISOLATE_CPU=1 +BIND_PID="" + +setup() +{ + ensure_command "taskset" "util-linux" + ensure_command "stress-ng" "stress-ng" + ensure_command "ps" "procps-ng" + + local cpu_num=$(cat /proc/cpuinfo | grep processor | wc -l) + if [ "$cpu_num" -le 2 ]; then + tst_brk $TCONF "need at least 3 CPUs for isolation test" + fi + + killall stress-ng 2>/dev/null || true + + # 绑定一个进程到隔离CPU + stress-ng --taskset $ISOLATE_CPU --cpu 1 --cpu-load 80 & + BIND_PID=$! + sleep 5 +} + +cleanup() +{ + killall stress-ng 2>/dev/null || true + rm -rf $0.log* 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local psr=$(ps -eo pid,psr | grep "^[[:space:]]*${BIND_PID}" | awk '{print $2}') + if [ "$psr" -eq "$ISOLATE_CPU" ]; then + tst_res $TPASS "bound process $BIND_PID running on isolated cpu $ISOLATE_CPU" + else + tst_res $TFAIL "bound process $BIND_PID running on cpu $psr, expected $ISOLATE_CPU" + fi + ;; + 1) + local cpu_num=$(cat /proc/cpuinfo | grep processor | wc -l) + # 排除隔离CPU,生成其他CPU的掩码范围 + local other_range="0,2-$((cpu_num - 1))" + stress-ng --taskset "$other_range" --cpu 4 --cpu-load 50 & + sleep 5 + + # 按PGID排除第一组stress-ng的所有进程(父进程+worker子进程) + local bind_pgid=$(ps -o pgid= -p $BIND_PID | tr -d ' ') + ps -eo pid,pgid,psr,args | grep "stress-ng" | grep -v grep | awk -v pgid="$bind_pgid" '$2 != pgid' > $0.log1 2>&1 + local intruders=$(awk -v cpu=$ISOLATE_CPU '$3 == cpu {count++} END {print count+0}' $0.log1) + if [ "$intruders" -eq 0 ]; then + tst_res $TPASS "no other process scheduled on isolated cpu $ISOLATE_CPU" + else + tst_res $TFAIL "$intruders other processes found on isolated cpu $ISOLATE_CPU" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/cpu/cpu_topology_tc001.sh b/os_smoke_src/testcases/kernel/cpu/cpu_topology_tc001.sh new file mode 100755 index 0000000..ad709d9 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/cpu_topology_tc001.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cpu_topology_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 验证CPU拓扑信息的正确性和一致性,包括socket、core、thread的层次关系 +# 前置条件: NA +# 步骤: +# 1. 通过lscpu获取socket数、每socket的core数、每core的thread数 +# 2. 验证 socket * cores_per_socket * threads_per_core == 总CPU数 +# 3. 检查sysfs中每个CPU的topology信息(core_id、physical_package_id)是否可读 +# 预期结果: +# 1. lscpu成功获取拓扑信息 +# 2. 拓扑数值计算一致 +# 3. 每个CPU的sysfs topology信息均可读 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "CPU拓扑数值一致性验证" + "sysfs topology信息可读性验证" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + if ! command -v lscpu > /dev/null 2>&1; then + tst_brk $TCONF "lscpu command not found" + fi +} + +cleanup() +{ + rm -rf $0.log* 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local sockets=$(lscpu | grep "Socket(s):" | awk '{print $2}') + local cores_per_socket=$(lscpu | grep "Core(s) per socket:" | awk '{print $4}') + local threads_per_core=$(lscpu | grep "Thread(s) per core:" | awk '{print $4}') + local total_cpus=$(lscpu | grep "^CPU(s):" | awk '{print $2}') + local expected=$((sockets * cores_per_socket * threads_per_core)) + + tst_res $TINFO "sockets=$sockets cores/socket=$cores_per_socket threads/core=$threads_per_core total=$total_cpus" + if [ "$expected" -eq "$total_cpus" ]; then + tst_res $TPASS "topology consistent: $sockets * $cores_per_socket * $threads_per_core = $total_cpus" + else + tst_res $TFAIL "topology mismatch: $sockets * $cores_per_socket * $threads_per_core = $expected != $total_cpus" + fi + ;; + 1) + local total_cpus=$(lscpu | grep "^CPU(s):" | awk '{print $2}') + local fail=0 + for i in $(seq 0 $((total_cpus - 1))); do + local topo_dir="/sys/devices/system/cpu/cpu${i}/topology" + if [ ! -d "$topo_dir" ]; then + tst_res $TFAIL "cpu${i} topology sysfs directory not found" + fail=1 + break + fi + for f in core_id physical_package_id; do + if [ ! -r "$topo_dir/$f" ]; then + tst_res $TFAIL "cpu${i}/topology/$f not readable" + fail=1 + break 2 + fi + local val=$(cat "$topo_dir/$f" 2>/dev/null) + if [ -z "$val" ]; then + tst_res $TFAIL "cpu${i}/topology/$f is empty" + fail=1 + break 2 + fi + done + done + [ $fail -eq 0 ] && tst_res $TPASS "all $total_cpus CPUs topology sysfs readable" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/cpu/hw_rand b/os_smoke_src/testcases/kernel/cpu/hw_rand new file mode 100755 index 0000000000000000000000000000000000000000..1c3dbacb85725dd7613796295363994e99bddf84 GIT binary patch literal 21512 zcmeHPdvILUc|ZHO(q2nmtt{D=ZNLlI)DN_J_$6aI-jyYJ1@a@d40eNkwc5RswqEV- z?B4Y&P+SZIVN8IbU^+?TKiU*J%)q3bRaXPV_E}AaR?9!#_I1o z_nh6mS9htCnYQDJjlca-F-c7my5~ZW?x~%ol(LZGQSy%-ojad zbug8M+4bx?Rt3b1Pnb)HnlVW~6|N=q2|W)e*>$*0pu-*J5lOEyVJ4}}AyGxhOcEBH zNjs*PtfY(uIY4%lSLWv|=ztj!v`ge8DyXz0mh1w;E+F(JJtHzEsVpCrjXwRtU%xvH zj`%PU*2TX|lyoUV?Vr z5L6VGwWN}R%a^vK63wY(y0EW#U&r$18e$)|KP;xAN;xh&iPx(R(=q@?9c(tX%5P2u_s0fPI?RVhGihP2 znW~P8)Ru0v4}F`I+KPgU|KWNuSyq^xR7rb{;Lp|#@EG+~YJVBdm*M68_(B<8EtNq( zEyIyOF*F4Rc${=)o(Kq*;j$kRzMu@3*An5aWqA4hrlSm(*E;F{y6&3KqKAIriyl^< zoDyWwdrunf@iWmwFZ)iBxERKlJqq#o{KxQJH#dwJ@gpRE_v|>r{0E4qtiOAP^LG za{5M)@#*iWLH5zZqr>4|ks*3*5h9 zyzY4S_|8*Yc<2{FQWz_oJKcSeGAzjZiw}2Sj2?|1X}p(EGL5>jQzTNPy6qDPvEZtYw6 z4)xvhE6CAHUwDkUZEuS9?Hl{7Z|swO=_kn&0`hCzk)c5P4BcLCF zUWJxw#MSm7=3&9Ikz?@RLJD2g0m54G`CDM}9x;zZ z_%s0<0A>C_kH6{Wx|+SdgKX{WRg0F)yN+P;mBME^@^v)<5r5!5_qwT7+mRqxl2bXB zK)wUCD0laB1(H95PYUv9E%LwSa>~PNgrl4~(H?)`o9=FZ(?gzafAEO6+rQvJCF*az zuPW;AI8?pfA5Qx_BL3Ehe?g}|*y(TT^ancqK3;CBp9SzE%Si4f!(Y#uVc=`4pmO0tI=$r|}|%nI>Up zykQ=bcvkQ--vdHVjy+}ixJ~l%Y!>=>tZ-h|=LNwp7jog||BcA^SbGNU6Yc%Dpx+hr zxS+2JN_Oklt-C3>0B>>&gB_t|q1Iq~YkOPkvbLqc1^s$DuBQzXZ*E_*@V8LzSP0+L zzWiMR6}Ub!&$T4m*4DO!v2l^Ur1U<;gQ=(F3h6WrD~987Tc#MZ>&DXQ%wRI@sLP+t zB_P^_y;EixhOYDiTuQJ{IvVSZ5*vTZ}ih_--wp ziseTbYp}=`#fP+PE@S9%BbiClg(I&);jd@Ymj2$J9c-M;W>{<&^Ebck+qKBnt=-Vt zv~ersw)|AnHBx-G^E_V5ySdF*W;AJNLphXO%VzM_spls8s-kz=dVF{=eVJv;>9JID zI8At^HkETulG@v8vQ6n4m~2z0YkKlJ#f8PMx5bm!X=L)=Yxad}C-1$#$InwH#`Ke8 zq(xWU7>UMsH14IJ=GI57`Iqggi1wv%T1nS=d|@G1c=Ahbj46%_T4|g>KP6MxCVX8Iv-ysR4aDp7xO`8*%;T<}Lc~?|B3JHYvHPjsdHtxG#^`8{RfwD{Rzn z0P!xV)L*>fPim!kNwLt;BS4X1n?jsddfh1va44nhI}E<0dd8 zD*fo|jq+S))izS=<&2^JkR&(P+y$~v{WD@VOxp}{PqIxl1;vC@ODUD zOp>qG>0s_xUm(ejsfR%xR-Y#3YcsQu996$ZYPZk&I+$bXTln_wK~D0(Dx@GFEjUEh?MGCl*ANTYU^F>uQ>yz%17G(s(d7< zr3RyXO{Jbf=xQ)E-{sNW6nz$!RlYrla@|LyZUghbNm^t6Zv(30jqP7c$`5eFpC|aR z8K?Q(>e;o_32J{1{GaftW7Vg*y)eXG>-H%{7jjP@@-``N<@z3$syQWnKuxQ6UgL(Wn z)vT&nS<{R46P|gjrsgZqK;zaSiuM)=F0?8_NUQn=M#R_P^7w0NOlubab!wTj4j4}< zc}K%HK)QMs8qDSDZ6N)r*TPQ=^!!W94A;&grMhd8>wv%&%AJYVhg@rCm9k9b2sb_6 zIg9*FgSt-^#*L*#4ds3bXe_t>gIT=|7W?T+SvE+01^|o0nIRO}Ord1`L?`mg?nA@4 z*@x5suFw5b@y)jfQFFBv@;Tc0(Sl##Td?CpTfVe8rzb{YhPex*rK2U8j;9I6Na#bM5gJNx6GYLQ^G2=^H?*GK zzHR{Me5DvHk{N(_DCGC&H9eP0VY7p2HX1WVga}$`qq%^uv?*iCBBr`!cl6A0DayS{ z?SCk)Ta=}(iu#UHTTm23xuHez|5&L#r!?N8%wle@JEtfhuA7zGZz!%EZHjtGSx`{a zHx!?5nxgJd0^d=Ty-KZb4Os`F>S<9_BEHqi^_(^f56SN+wLgLnH+EIHG%YhUq#4+U zu}Q=7nS_=#a!kXv5A5s6ClBZ(%8z7n#$aKH=_Bm7u)mb-3i2S<)`bF&fM5#=;?LA(+zcl$@T|cmdGt+}Yk-(!dfUl$9C}3&atj zQU)y%tJvz}+gZ14>@X4ZJ+AxC?Y30+IXj2P4Q7__HGR)f7?o6Uu4!6uw6YZ=Lm z>bSD7lS(u6yrJa_sDpfqVJ@G`E8o%JWwiUJ&brY8P zbu0`vj~a!vzJ@RGPsYKB_|RCF%`Sf4uE^KIzi`^5DT8p~D@X zpya{rq2CerFvAh|7T2GaJM&;x?u4U$6_e{zt@_o>X^s2(<*$^BwheavVDoczyZVMn})bvfenyqg$2ShXAt9Gf8UFuA(f zD)JO}soHTlqOs%GSOLL#N-I-~ay;|!82itLmv#)M9IOyF{ERO%Nh z;72OpH2x+3c0Z>o;4~6;(*I2byp{^=;Ge(y0cT>|N#+#R2z~kciHS0{qk^A81^iDd z;KwWAZ&tuRuYk|Rh{dT~^itu3?-aPby!QxP{uU(5`!sN;e)7D~mvi(||0e>M@Anda zTj2Kg{jUo6bPT+l^50$o-&z6BRKUMk0e`9j{))iuB6E3@VAXVU-80uNV(CB*4YfgiNtdEhQqx?fRuqHs6IC*D7w7JlSD4pW=4 z=ehpG`|#_)o$`N9_>qHOdFQHB-VeF|dbV^*SSDGdPX6&IMVL0>{$CH=<#7L>E%fcr zk!Frhd>)0lpNY?fTY*nQyU-3;3fvrYh~pfxeC?}%9|0b;XqwD#I6m>Y^K=D2KLQ@W zE))CnXn^$H6Q9d}U%}76RKPzL{_W2}Hx~Un<-Z91ozmd1wYs z^=W_q4-5Qkb(rhvraIfqalw?4est^r`gzp$T&6<|rj_02Q5+sVM}*Rq_oWtr-vNE{ zW3SKqMSkp)9Y=uIyJt>F_etPE;j^6FOn%%BD5ivM_P#&HSpoY5A_u#ng^ zq9xNw124HYUZat@4GWEA3_C`8PI#!0P9=Bisr`-ym4qRFYCs|DXjDNK()T5uvt<>) z+(^dp5{uWY^72}7Yq_m5YdNj5iS=NWJJg3&fjgL3FIJ_A^<$NDCxCKBc^z7HZ1rUo zatGFW(-JAO(qecc7TVdQZf2#_P?glEAL*%~X5P&*TT2E2C-l7%Mj3I^wYGs(zmm+M z?rnw>>%F8fvCc~}=lU+mZFOA|IMuUxZbm#`Fsp?(W2p+nu^L6OrGCnUL#XHpZBug1 z#bk@yK;9_eWP>;>oy4ITqu2n3vp6tOp(Qffa4ItxOKAxslgn$d!af$yjAm21p(jEs zmb9;MR>A~MI;q8Sx!8Ub)5z_|0U)$tD^VC7-4By8j+fM;`sH&Jn%1)=vY}h+-q@w# zY!ItCW^TH6Y>aH^U1yb;6{%^MIMBMI!b7xc3)9y3ZR(8lX`6a_ws!Yx{gKYTZt}{{ zFQJO2g7DKz!r}7cOT@V+R`AQtG@;W{Y}z`XpfOKQv5D;Gr`W}0X`YXQyjl-S(GvNL zHiAhkI!J{kr)Y{xqiGe#xw%qF>FN{*xY&|&zWR~!U>-9+bgL0FCmN1%kqtxVyEq@( z63Xu%#iSHN7tNV;M8+_GrRQ+#Ph+29DAL*6Y{Z5IH=Hhn1`A1?^pnJ0fw&Rcr5H-= zPs5){F;~YWdvJ6EZBi^}G{|t{kCB0hWm9-)@M4DyeIMeyu|v5GZ*nfawfX#X`7rMsYcq`Qn8M#S9~t94uhM3`Nif3&Zdh zOAnXWWO@isY9SgN%;|e1hj}_(#!-Rg!-d~DPBRM_64Asq4{}~ho{uH>O$#yCm2sli z<5P`~w3qW=^8BeFWaUAQ=OosKNV&b7Co_b@c~VhOyL}h%Pq4RHrhn8O=9)(ZAoDNB zbM$*>dDsXBKcE!zFX!D(2>S~{8N)GNc4^-v@LNIoE1uxx{2YHz1V%rCmItcq@~{gG z?bVj~m-BebML`>dvdq8KlXN%awD()`a=ve~un!7*d;SH?_)orIq`jOM%nJJfp(xY0 zm;WBfNwrhxo$-cw?2NGACvdW-^a|WB8QYXT^ME zR@e^|HO&ZLD~qV4k0D-eFUR?3g#Ep;VuT-QCs;Aj$5Bp6x5#nahtuIR}HA9!2-%$S&;1~p_7Q<$IjB3 zTK)lW2m~V7^(8OqYtW%Jv2+blkmoh{F4O)E*ihe*_HsT~o|`An$CLd_rYqa=ZP-!~ zrM;Z@JxiHF2=edJm$aAk-ytfuFU}8(1i~d#9+P&Geqghg@6TajZ*MP3t2}%RhJ4BV z%lFZ)%e4Otw&b%FpYkB>|F9fHT&6Fn2NAk|%OK~kkBRg@k`p>Y!CszOu$R8152BBS zOwOJ!yDs%lJ4Ti-3;SQmiZR_y&|O-={`q#(V5iNwjb)t`?D11N4-eQ3Y%JR-?Cs_J z)-u!nlDsjPY%%}R4^f*6*F4r?8XTQs(rZDs<0JcBlL_NI%~IbH5GNh$Ozv;wg(HMA bU8zfFF4(G=i{7V269{}h+>G*XyZwIyzwmLe literal 0 HcmV?d00001 diff --git a/os_smoke_src/testcases/kernel/cpu/hw_rand_tc001.c b/os_smoke_src/testcases/kernel/cpu/hw_rand_tc001.c new file mode 100755 index 0000000..2bc16e6 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/hw_rand_tc001.c @@ -0,0 +1,115 @@ +/***************************************************# + * SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (c) 2026 Tencent. All Rights Reserved. + * Author: kevinzcheng + * Date: 2026/03/11 + +# 用例名称:hw_rand +# 描述: CPU内置安全功能支持CPU硬件密码运算与随机数生成等功能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: +# BIOS中对RDRAND进行配置,RDRAND Control 设置为Enabled +# 步骤: +# 1,调用rdrand指令生成128个随机数 +# 2,检查随机数重复性 +# 预期结果: +# 1,正确生成128个随机数 +# 2,随机数无重复 +#**************************************************# +*/ + +#include +#include +#include +#include + +#include "../../../../tools/case_lib.c" + +#define NUM_SAMPLES 128 + +unsigned int tcnt = 2; + +static uint32_t rand_nums[NUM_SAMPLES]; + +static int rdrand32_step(uint32_t *rand) +{ + unsigned char ok; + + asm volatile("rdrand %0; setc %1" + : "=r"(*rand), "=qm"(ok)); + + return (int)ok; +} + +static int rdrand_get_n_uints(uint32_t *nums, int n) +{ + int i; + + for (i = 0; i < n; i++) { + if (!rdrand32_step(&nums[i])) + return -1; + } + return 0; +} + +static void setup(void) +{ + /* 尝试一次 rdrand 检查是否可用 */ + uint32_t test_val; + + if (!rdrand32_step(&test_val)) + tst_brk(TCONF, "rdrand instruction not supported or disabled in BIOS"); +} + +static void cleanup(void) +{ +} + +static void run_test(unsigned int n) +{ + switch (n) { + case 0: { + /* 步骤1: 生成128个硬件随机数 */ + if (rdrand_get_n_uints(rand_nums, NUM_SAMPLES) != 0) { + tst_res(TFAIL, "failed to generate %d hardware random numbers", NUM_SAMPLES); + return; + } + + tst_res(TINFO, "generated %d hardware random numbers", NUM_SAMPLES); + tst_res(TPASS, "rdrand generated %d random numbers successfully", NUM_SAMPLES); + break; + } + case 1: { + /* 步骤2: 检查随机数唯一性 */ + int i, j; + int dup_found = 0; + + /* 重新生成以确保数据新鲜 */ + if (rdrand_get_n_uints(rand_nums, NUM_SAMPLES) != 0) { + tst_res(TFAIL, "failed to generate random numbers for uniqueness check"); + return; + } + + for (i = 0; i < NUM_SAMPLES && !dup_found; i++) { + for (j = i + 1; j < NUM_SAMPLES; j++) { + if (rand_nums[i] == rand_nums[j]) { + dup_found = 1; + tst_res(TFAIL, "duplicate random number found: nums[%d]=nums[%d]=%u", + i, j, rand_nums[i]); + break; + } + } + } + + if (!dup_found) + tst_res(TPASS, "%d hardware random numbers are all unique", NUM_SAMPLES); + break; + } + } +} + +int main(int argc, char *argv[]) +{ + return test_main(argc, argv); +} diff --git a/os_smoke_src/testcases/kernel/cpu/hw_rand_tc001.sh b/os_smoke_src/testcases/kernel/cpu/hw_rand_tc001.sh new file mode 100755 index 0000000..b772bf6 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/hw_rand_tc001.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: hw_rand_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: CPU内置安全功能支持CPU硬件密码运算与随机数生成等功能 +# 前置条件: +# BIOS中对RDRAND进行配置,RDRAND Control 设置为Enabled +# 步骤: +# 1. 调用rdrand指令生成128个随机数 +# 2. 检查随机数重复性 +# 预期结果: +# 1. 正确生成128个随机数 +# 2. 随机数无重复 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=("硬件随机数生成与唯一性验证") +tcnt=${#TCASE_DESC[@]} + +setup() +{ + local arch=$(uname -m) + if [ "$arch" = "aarch64" ] || echo "$arch" | grep -qi "arm"; then + tst_brk $TCONF "ARM architecture does not support RDRAND, skip" + fi + + if [ ! -x ./hw_rand_tc001 ]; then + tst_brk $TCONF "hw_rand_tc001 binary not found" + fi +} + +cleanup() +{ + return 0 +} + +run_test() +{ + local n=$1 + + ./hw_rand_tc001 + local ret=$? + if [ $ret -eq 0 ]; then + tst_res $TPASS "hw_rand_tc001 all tests passed" + elif [ $ret -eq 32 ]; then + tst_res $TCONF "hw_rand_tc001 skipped (rdrand not supported)" + else + tst_res $TFAIL "hw_rand_tc001 failed with exit code $ret" + fi +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/cpu/mutex_protect_tc001.c b/os_smoke_src/testcases/kernel/cpu/mutex_protect_tc001.c new file mode 100755 index 0000000..0eba4a2 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/mutex_protect_tc001.c @@ -0,0 +1,187 @@ +/* *************************************************# + * SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (c) 2026 Tencent. All Rights Reserved. + * Author: kevinzcheng + * Date: 2026/03/11 + +# 用例名称:mutex_protect +# 描述: CPU内置安全功能支持通过硬件指令判别临界区冲突 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: +# NA +# 步骤: +# 1,创建核数个线程 +# 2,无锁保护下所有线程操作共享数据 +# 3,有锁保护下所有线程操作共享数据 +# 预期结果: +# 1,线程启动成功 +# 2,无锁保护下共享数据各线程出现数据不一致 +# 3,有锁保护下共享数据各线程不会出现数据不一致 +#**************************************************# +*/ + +#include +#include +#include +#include +#include +#include + +#include "../../../../tools/case_lib.c" + +#define CACHE_LINE_SIZE 64 + +struct _g_data_ { + long top_half_data; + long pad[CACHE_LINE_SIZE / sizeof(long)]; + long bottom_half_data; +} g_data; + +static int elapsed_time_threshold = 60; +static int g_fsm; +static int g_stop_flag = 0; + +static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; + +unsigned int tcnt = 3; + +static int cpu_cnt; +static pthread_t *threads; + +static void *thread_function(void *arg) +{ + intptr_t i = (intptr_t)arg; + int data = (int)i; + + /* stage 0: without protection */ + while (g_stop_flag == 0) { + g_data.top_half_data = data; + g_data.bottom_half_data = data; + } + + /* stage 1: with protection */ + while (g_stop_flag == 1 && g_fsm == 1) { + pthread_mutex_lock(&g_mutex); + g_data.top_half_data = data; + g_data.bottom_half_data = data; + pthread_mutex_unlock(&g_mutex); + } + return NULL; +} + +static void setup(void) +{ + cpu_cnt = sysconf(_SC_NPROCESSORS_ONLN); + if (cpu_cnt < 2) + tst_brk(TCONF, "need at least 2 CPUs, got %d", cpu_cnt); + + threads = malloc((cpu_cnt - 1) * sizeof(pthread_t)); + if (!threads) + tst_brk(TFAIL, "malloc failed for thread array"); + + tst_res(TINFO, "CPU count: %d, will create %d threads", cpu_cnt, cpu_cnt - 1); +} + +static void cleanup(void) +{ + if (threads) { + free(threads); + threads = NULL; + } +} + +static void run_test(unsigned int n) +{ + int i; + time_t start_time, current_time; + int elapsed_time; + + switch (n) { + case 0: { + /* 步骤1: 创建线程 */ + g_fsm = 0; + g_stop_flag = 0; + g_data.top_half_data = 0; + g_data.bottom_half_data = 0; + + for (i = 0; i < cpu_cnt - 1; i++) { + intptr_t arg = (intptr_t)i; + if (pthread_create(&threads[i], NULL, thread_function, (void *)arg) != 0) { + tst_res(TFAIL, "failed to create thread %d", i); + g_fsm = 2; + return; + } + } + tst_res(TPASS, "successfully created %d threads", cpu_cnt - 1); + sleep(2); + + /* 步骤2: 无锁保护检测数据不一致 */ + start_time = time(NULL); + elapsed_time = 0; + int mismatch_found = 0; + + while (1) { + if (g_data.top_half_data != g_data.bottom_half_data) { + mismatch_found = 1; + break; + } + current_time = time(NULL); + elapsed_time = difftime(current_time, start_time); + if (elapsed_time > elapsed_time_threshold) + break; + } + + /* 进入 stage 1 */ + g_fsm = 1; + g_stop_flag = 1; + sleep(3); + + if (mismatch_found) + tst_res(TPASS, "detected data mismatch without mutex protection (expected)"); + else + tst_res(TFAIL, "no data mismatch detected without mutex protection after %ds", elapsed_time_threshold); + break; + } + case 1: + /* 已在 case 0 中完成并报告 */ + break; + case 2: { + /* 步骤3: 有锁保护检测数据一致性 */ + start_time = time(NULL); + elapsed_time = 0; + int bad_mismatch = 0; + + while (1) { + pthread_mutex_lock(&g_mutex); + if (g_data.top_half_data != g_data.bottom_half_data) + bad_mismatch = 1; + pthread_mutex_unlock(&g_mutex); + + if (bad_mismatch) + break; + + current_time = time(NULL); + elapsed_time = difftime(current_time, start_time); + if (elapsed_time > elapsed_time_threshold) + break; + } + + /* 停止所有线程 */ + g_fsm = 2; + for (i = 0; i < cpu_cnt - 1; i++) + pthread_join(threads[i], NULL); + + if (bad_mismatch) + tst_res(TFAIL, "data mismatch detected WITH mutex protection (unexpected)"); + else + tst_res(TPASS, "no data mismatch with mutex protection after %ds", elapsed_time_threshold); + break; + } + } +} + +int main(int argc, char *argv[]) +{ + return test_main(argc, argv); +} diff --git a/os_smoke_src/testcases/kernel/cpu/mutex_protect_tc001.sh b/os_smoke_src/testcases/kernel/cpu/mutex_protect_tc001.sh new file mode 100755 index 0000000..8e92ea8 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/mutex_protect_tc001.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: mutex_protect_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: CPU内置安全功能支持通过硬件指令判别临界区冲突 +# 前置条件: NA +# 步骤: +# 1. 创建核数个线程 +# 2. 无锁保护下所有线程操作共享数据 +# 3. 有锁保护下所有线程操作共享数据 +# 预期结果: +# 1. 线程启动成功 +# 2. 无锁保护下共享数据各线程出现数据不一致 +# 3. 有锁保护下共享数据各线程不会出现数据不一致 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=("互斥锁临界区保护测试") +tcnt=${#TCASE_DESC[@]} + +setup() +{ + if [ ! -x ./mutex_protect_tc001 ]; then + tst_brk $TCONF "mutex_protect_tc001 binary not found" + fi +} + +cleanup() +{ + return 0 +} + +run_test() +{ + local n=$1 + + ./mutex_protect_tc001 + local ret=$? + if [ $ret -eq 0 ]; then + tst_res $TPASS "mutex_protect_tc001 all tests passed" + elif [ $ret -eq 32 ]; then + tst_res $TCONF "mutex_protect_tc001 skipped (precondition not met)" + else + tst_res $TFAIL "mutex_protect_tc001 failed with exit code $ret" + fi +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/cpu/numa_access_cpu_tc001.c b/os_smoke_src/testcases/kernel/cpu/numa_access_cpu_tc001.c new file mode 100755 index 0000000..398a12a --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/numa_access_cpu_tc001.c @@ -0,0 +1,203 @@ +/*************************************************# + * SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (c) 2026 Tencent. All Rights Reserved. + * Author: kevinzcheng + * Date: 2026/03/11 + +# 用例名称:numa_access_cpu +# 描述: 物理多路CPU支持NUMA架构,提供NUMA驱动编程接口,根据ACPI描述的NUMA架构内存信息进行进程调度优化,让进程运行在本NUMA节点之上 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: +# yum install -y numactl numactl-libs numactl-devel +# 步骤: +# 1,检查机器numa节点数 +# 2,按node数创建多个线程,每个线程分别通过numa_run_on_node绑定node,申请内存、读写内存、释放内存 +# 3,检查各线程是否正确运行在指定的node上 +# 预期结果: +# 1,numa node 要求不少于2 +# 2,每个线程分别运行在各个指定的node上,申请、读写、释放内存无异常 +# 3,各个线程都正确运行在指定的node上 +#**************************************************# +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../../../tools/case_lib.c" + +#define NODE_MIN_FREEMEM (32 * 1024 * 1024) + +unsigned int tcnt = 3; + +static int num_nodes; + +static void *thread_func(void *arg) +{ + int i; + char *mem; + char c = 'a'; + int rn; + int node = (int)(intptr_t)arg; + + if (numa_run_on_node(node)) { + fprintf(stderr, "numa_run_on_node(%d) failed\n", node); + return (void *)(intptr_t)-1; + } + + sleep(10); + rn = numa_node_of_cpu(sched_getcpu()); + + mem = numa_alloc_onnode(NODE_MIN_FREEMEM * sizeof(char), node); + if (mem == NULL) { + fprintf(stderr, "unable to allocate memory on node %d\n", node); + return (void *)(intptr_t)-1; + } + + numa_tonode_memory(mem, NODE_MIN_FREEMEM * sizeof(char), node); + c = c + node; + memset(mem, c, NODE_MIN_FREEMEM * sizeof(char)); + + for (i = 0; i < NODE_MIN_FREEMEM; i += 1048576) { + if (mem[i] != c) { + fprintf(stderr, "data mismatch on node %d\n", node); + numa_free(mem, NODE_MIN_FREEMEM); + return (void *)(intptr_t)-1; + } + } + + rn = numa_node_of_cpu(sched_getcpu()); + numa_free(mem, NODE_MIN_FREEMEM); + + return (void *)(intptr_t)rn; +} + +static int check_numa_cpus_exclusive(int nodes) +{ + int i, j, cpu; + struct bitmask *mask_i, *mask_j; + + for (i = 0; i < nodes; i++) { + mask_i = numa_allocate_cpumask(); + if (numa_node_to_cpus(i, mask_i) < 0) { + numa_free_cpumask(mask_i); + continue; + } + for (j = i + 1; j < nodes; j++) { + mask_j = numa_allocate_cpumask(); + if (numa_node_to_cpus(j, mask_j) < 0) { + numa_free_cpumask(mask_j); + continue; + } + for (cpu = 0; cpu < numa_num_configured_cpus(); cpu++) { + if (numa_bitmask_isbitset(mask_i, cpu) && + numa_bitmask_isbitset(mask_j, cpu)) { + numa_free_cpumask(mask_j); + numa_free_cpumask(mask_i); + return -1; + } + } + numa_free_cpumask(mask_j); + } + numa_free_cpumask(mask_i); + } + return 0; +} + +static void setup(void) +{ + num_nodes = numa_max_node() + 1; + if (num_nodes < 2) + tst_brk(TCONF, "numa node number should be >= 2, got %d", num_nodes); + + if (check_numa_cpus_exclusive(num_nodes) != 0) + tst_brk(TCONF, "each NUMA node must have exclusive CPUs (VM environment not supported)"); + + tst_res(TINFO, "numa node number %d", num_nodes); +} + +static void cleanup(void) +{ +} + +static void run_test(unsigned int n) +{ + switch (n) { + case 0: { + /* 步骤1: 检查numa节点数 */ + if (num_nodes >= 2) + tst_res(TPASS, "numa node count %d >= 2", num_nodes); + else + tst_res(TFAIL, "numa node count %d < 2", num_nodes); + break; + } + case 1: { + /* 步骤2: 创建线程绑定node,申请/读写/释放内存 */ + int node; + pthread_t thread[512]; + int fail = 0; + + for (node = 0; node < num_nodes; node++) { + if (pthread_create(&thread[node], NULL, thread_func, + (void *)(intptr_t)node) != 0) { + tst_res(TFAIL, "pthread_create for node %d failed", node); + return; + } + } + + for (node = 0; node < num_nodes; node++) { + void *ret; + pthread_join(thread[node], &ret); + if ((intptr_t)ret == -1) { + tst_res(TFAIL, "thread for node %d reported failure", node); + fail = 1; + } + } + + if (!fail) + tst_res(TPASS, "all threads completed memory operations successfully"); + break; + } + case 2: { + /* 步骤3: 验证线程运行在指定node上 */ + int node; + pthread_t thread[512]; + int fail = 0; + + for (node = 0; node < num_nodes; node++) { + if (pthread_create(&thread[node], NULL, thread_func, + (void *)(intptr_t)node) != 0) { + tst_res(TFAIL, "pthread_create for node %d failed", node); + return; + } + } + + for (node = 0; node < num_nodes; node++) { + void *ret; + pthread_join(thread[node], &ret); + int actual_node = (int)(intptr_t)ret; + if (actual_node != node) { + tst_res(TFAIL, "thread %d ran on node %d instead of node %d", + node, actual_node, node); + fail = 1; + } + } + + if (!fail) + tst_res(TPASS, "all threads correctly ran on their designated NUMA nodes"); + break; + } + } +} + +int main(int argc, char *argv[]) +{ + return test_main(argc, argv); +} diff --git a/os_smoke_src/testcases/kernel/cpu/numa_access_cpu_tc001.sh b/os_smoke_src/testcases/kernel/cpu/numa_access_cpu_tc001.sh new file mode 100755 index 0000000..fa035ac --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/numa_access_cpu_tc001.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: numa_access_cpu_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: NUMA架构驱动编程接口,根据ACPI描述进行进程调度优化 +# 前置条件: yum install -y numactl numactl-libs numactl-devel +# 步骤: +# 1. 检查机器numa节点数 +# 2. 创建线程绑定node,申请/读写/释放内存 +# 3. 验证线程运行在指定node上 +# 预期结果: +# 1. numa node 要求不少于2 +# 2. 每个线程分别运行在各个指定的node上,操作内存无异常 +# 3. 各个线程都正确运行在指定的node上 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=("NUMA CPU绑定内存访问测试") +tcnt=${#TCASE_DESC[@]} + +setup() +{ + if [ ! -x ./numa_access_cpu_tc001 ]; then + tst_brk $TCONF "numa_access_cpu_tc001 binary not found" + fi +} + +cleanup() +{ + return 0 +} + +run_test() +{ + local n=$1 + + ./numa_access_cpu_tc001 + local ret=$? + if [ $ret -eq 0 ]; then + tst_res $TPASS "numa_access_cpu_tc001 all tests passed" + elif [ $ret -eq 32 ]; then + tst_res $TCONF "numa_access_cpu_tc001 skipped (precondition not met)" + else + tst_res $TFAIL "numa_access_cpu_tc001 failed with exit code $ret" + fi +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/cpu/numa_access_tc001.c b/os_smoke_src/testcases/kernel/cpu/numa_access_tc001.c new file mode 100755 index 0000000..6c000ff --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/numa_access_tc001.c @@ -0,0 +1,133 @@ +/*************************************************# + * SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (c) 2026 Tencent. All Rights Reserved. + * Author: kevinzcheng + * Date: 2026/03/11 + +# 用例名称:numa_access +# 描述: 物理多路CPU支持跨路内存访问能力,允许其他路CPU访问本路内存 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: +# yum install -y numactl numactl-libs numactl-devel +# 步骤: +# 1,检查机器numa节点数 +# 2,分别在每个node上申请内存、读写内存、释放内存 +# 预期结果: +# 1,numa node 要求不少于2 +# 2,每个节点申请、读写、释放内存无异常 +#**************************************************# +*/ + +#include +#include +#include +#include + +#include "../../../../tools/case_lib.c" + +#define NODE_MIN_FREEMEM (32 * 1024 * 1024) + +unsigned int tcnt = 2; + +static int num_nodes; + +/* 检查各NUMA节点的CPU是否互斥(排除VM中多节点共享相同CPU的情况) */ +static int check_numa_cpus_exclusive(int nodes) +{ + int i, j, cpu; + struct bitmask *mask_i, *mask_j; + + for (i = 0; i < nodes; i++) { + mask_i = numa_allocate_cpumask(); + if (numa_node_to_cpus(i, mask_i) < 0) { + numa_free_cpumask(mask_i); + continue; + } + for (j = i + 1; j < nodes; j++) { + mask_j = numa_allocate_cpumask(); + if (numa_node_to_cpus(j, mask_j) < 0) { + numa_free_cpumask(mask_j); + continue; + } + for (cpu = 0; cpu < numa_num_configured_cpus(); cpu++) { + if (numa_bitmask_isbitset(mask_i, cpu) && + numa_bitmask_isbitset(mask_j, cpu)) { + numa_free_cpumask(mask_j); + numa_free_cpumask(mask_i); + return -1; + } + } + numa_free_cpumask(mask_j); + } + numa_free_cpumask(mask_i); + } + return 0; +} + +static void setup(void) +{ + num_nodes = numa_max_node() + 1; + if (num_nodes < 2) + tst_brk(TCONF, "numa node number should be >= 2, got %d", num_nodes); + + if (check_numa_cpus_exclusive(num_nodes) != 0) + tst_brk(TCONF, "each NUMA node must have exclusive CPUs (VM environment not supported)"); + + tst_res(TINFO, "numa node number %d", num_nodes); +} + +static void cleanup(void) +{ +} + +static void run_test(unsigned int n) +{ + switch (n) { + case 0: { + /* 步骤1: 检查numa节点数 */ + if (num_nodes >= 2) + tst_res(TPASS, "numa node count %d >= 2", num_nodes); + else + tst_res(TFAIL, "numa node count %d < 2", num_nodes); + break; + } + case 1: { + /* 步骤2: 在每个node上申请内存、读写、释放 */ + int node; + int fail = 0; + char c = 'a'; + + for (node = 0; node < num_nodes; node++) { + char *mem = numa_alloc_onnode(NODE_MIN_FREEMEM * sizeof(char), node); + if (mem == NULL) { + tst_res(TFAIL, "unable to allocate memory on node %d", node); + return; + } + numa_tonode_memory(mem, NODE_MIN_FREEMEM * sizeof(char), node); + c = 'a' + node + 1; + memset(mem, c, NODE_MIN_FREEMEM * sizeof(char)); + + int i; + for (i = 0; i < NODE_MIN_FREEMEM; i += 1048576) { + if (mem[i] != c) { + tst_res(TFAIL, "data mismatch on node %d at offset %d", node, i); + fail = 1; + break; + } + } + tst_res(TINFO, "read %c from node %d OK", c, node); + numa_free(mem, NODE_MIN_FREEMEM); + } + + if (!fail) + tst_res(TPASS, "cross-node memory alloc/read/write/free all passed"); + break; + } + } +} + +int main(int argc, char *argv[]) +{ + return test_main(argc, argv); +} diff --git a/os_smoke_src/testcases/kernel/cpu/numa_access_tc001.sh b/os_smoke_src/testcases/kernel/cpu/numa_access_tc001.sh new file mode 100755 index 0000000..036bcf7 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/numa_access_tc001.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: numa_access_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 物理多路CPU支持跨路内存访问能力,允许其他路CPU访问本路内存 +# 前置条件: yum install -y numactl numactl-libs numactl-devel +# 步骤: +# 1. 检查机器numa节点数 +# 2. 分别在每个node上申请内存、读写内存、释放内存 +# 预期结果: +# 1. numa node 要求不少于2 +# 2. 每个节点申请、读写、释放内存无异常 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=("NUMA跨路内存访问测试") +tcnt=${#TCASE_DESC[@]} + +setup() +{ + if [ ! -x ./numa_access_tc001 ]; then + tst_brk $TCONF "numa_access_tc001 binary not found" + fi +} + +cleanup() +{ + return 0 +} + +run_test() +{ + local n=$1 + + ./numa_access_tc001 + local ret=$? + if [ $ret -eq 0 ]; then + tst_res $TPASS "numa_access_tc001 all tests passed" + elif [ $ret -eq 32 ]; then + tst_res $TCONF "numa_access_tc001 skipped (precondition not met)" + else + tst_res $TFAIL "numa_access_tc001 failed with exit code $ret" + fi +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/cpu/soft_cryp_tc001.c b/os_smoke_src/testcases/kernel/cpu/soft_cryp_tc001.c new file mode 100755 index 0000000..9d8d588 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/soft_cryp_tc001.c @@ -0,0 +1,177 @@ +/*# **************************************************# +# 用例名称:soft_cryp +# 描述: CPU内置安全功能提供编程接口供应用程序调用 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: +# yum install openssl-devel -y +# 步骤: +# 1,往soft_cryp_tc001.cfg输入任意字符,已有默认值tencentos +# 2,对该文件内容进行加密,并保存在sf_encrypted.txt,核对加密前后内容 +# 3,对加密文件进行解密,并保持在sf_decrypted.txt,核对解密前后内容 +# 预期结果: +# 2,加密成功,加密后内容与原内容不一致 +# 3,解密成功,解密后内容与原内容一致 +# 关联tapd需求: +# NA +# author: shankslong +# 修改记录: Created on 2023-04-13 +#**************************************************#*/ + +#include +#include +#include +#include +#include + +#define SM4_KEY_LENGTH 16 + +#define SM4KEY_FILE "sm4key.txt" +#define INPUT_FILE "soft_cryp_tc001.cfg" +#define ENCRYPTED_FILE "sf_encrypted.txt" +#define DECRYPTED_FILE "sf_decrypted.txt" + +int main(int argc, char *argv[]) +{ + int ret = 0; + + // 读取文件A + FILE *input_file = fopen(INPUT_FILE, "rb"); + if (!input_file) { + printf("Failed to open input file: %s\n", INPUT_FILE); + ret++ ; + goto done; + } + + fseek(input_file, 0, SEEK_END); + long input_file_size = ftell(input_file); + fseek(input_file, 0, SEEK_SET); + unsigned char *input_data = malloc(input_file_size); + fread(input_data, 1, input_file_size, input_file); + fclose(input_file); + + // 生成密钥 + unsigned char key[SM4_KEY_LENGTH] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10}; + // 输出密钥到文件 + FILE *key_file = fopen(SM4KEY_FILE, "wb"); + if (!key_file) { + printf("Failed to open sm4key file: %s\n", SM4KEY_FILE); + ret++; + goto free_input; + } + fwrite(key, 1, SM4_KEY_LENGTH, key_file); + fclose(key_file); + + // 初始化加密上下文 + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + EVP_CIPHER_CTX_init(ctx); + EVP_EncryptInit_ex(ctx, EVP_sm4_ecb(), NULL, key, NULL); + + // 加密数据 + unsigned char *output_data = malloc(input_file_size + EVP_MAX_BLOCK_LENGTH); + int output_data_size = 0; + EVP_EncryptUpdate(ctx, output_data, &output_data_size, input_data, input_file_size); + int final_output_data_size = 0; + EVP_EncryptFinal_ex(ctx, output_data + output_data_size, &final_output_data_size); + output_data_size += final_output_data_size; + + // 输出密文到文件 + FILE *output_file = fopen(ENCRYPTED_FILE, "wb"); + if (!output_file) { + printf("Failed to open output file: %s\n", ENCRYPTED_FILE); + ret++; + goto free_output; + } + fwrite(output_data, 1, output_data_size, output_file); + fclose(output_file); + printf("\n加密前内容:\n"); + input_data[input_file_size]='\0'; + printf("%s", input_data); + printf("\n加密后内容:\n"); + output_data[output_data_size]='\0'; + printf("%s", output_data); + printf("\n"); + + if (memcmp(input_data, output_data, input_file_size) == 0 || memcmp(input_data, output_data, output_data_size) == 0) { + printf("加密不对\n"); + ret++; + goto free_output; + } else { + printf("加密成功\n"); + } + + // 初始化解密上下文 + EVP_DecryptInit_ex(ctx, EVP_sm4_ecb(), NULL, key, NULL); + + // 读取密文文件 + FILE *encrypted_file = fopen(ENCRYPTED_FILE, "rb"); + if (!encrypted_file) { + printf("Failed to open encrypted file: %s\n", ENCRYPTED_FILE); + ret++; + goto done; + } + fseek(encrypted_file, 0, SEEK_END); + long encrypted_file_size = ftell(encrypted_file); + fseek(encrypted_file, 0, SEEK_SET); + unsigned char *encrypted_data = malloc(encrypted_file_size); + fread(encrypted_data, 1, encrypted_file_size, encrypted_file); + fclose(encrypted_file); + + // 解密数据 + unsigned char *decrypted_data = malloc(encrypted_file_size + EVP_MAX_BLOCK_LENGTH); + int decrypted_data_size = 0; + EVP_DecryptUpdate(ctx, decrypted_data, &decrypted_data_size, encrypted_data, encrypted_file_size); + int final_decrypted_data_size = 0; + EVP_DecryptFinal_ex(ctx, decrypted_data + decrypted_data_size, &final_decrypted_data_size); + decrypted_data_size += final_decrypted_data_size; + + // 输出解密结果到文件 + FILE *decrypted_file = fopen(DECRYPTED_FILE, "wb"); + if (!decrypted_file) { + printf("Failed to open decrypted file: %s\n", DECRYPTED_FILE); + ret++; + goto free_encdata; + } + fwrite(decrypted_data, 1, decrypted_data_size, decrypted_file); + fclose(decrypted_file); + + // 输出密钥和加解密结果 + printf("已有欲加密文件: %s\n", INPUT_FILE); + printf("密文写入文件: %s\n", ENCRYPTED_FILE); + printf("解密写入文件: %s\n", DECRYPTED_FILE); + printf("SM4密钥写入文件: %s\n", SM4KEY_FILE); + + printf("SM4-ECB密钥: "); + for (int i = 0; i < SM4_KEY_LENGTH; i++) { + printf("%02x", key[i]); + } + printf("\n解密后内容:\n"); + decrypted_data[decrypted_data_size]='\0'; + printf("%s", decrypted_data); + printf("\n"); + + if (memcmp(input_data, decrypted_data, input_file_size) != 0 || memcmp(input_data, decrypted_data, decrypted_data_size) != 0) { + printf("解密不对\n"); + ret++; + } else { + printf("解密成功\n"); + } + // 清理资源 + free(decrypted_data); +free_encdata: + free(encrypted_data); +free_output: + free(output_data); + EVP_CIPHER_CTX_free(ctx); +free_input: + free(input_data); + +done: + if (ret == 0) { + remove(ENCRYPTED_FILE); + remove(DECRYPTED_FILE); + remove(SM4KEY_FILE); + } + + return ret; +} diff --git a/os_smoke_src/testcases/kernel/cpu/soft_cryp_tc001.cfg b/os_smoke_src/testcases/kernel/cpu/soft_cryp_tc001.cfg new file mode 100755 index 0000000..b89a351 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/soft_cryp_tc001.cfg @@ -0,0 +1 @@ +tencentos diff --git a/os_smoke_src/testcases/kernel/cpu/soft_cryp_tc001.sh b/os_smoke_src/testcases/kernel/cpu/soft_cryp_tc001.sh new file mode 100755 index 0000000..37ee332 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/soft_cryp_tc001.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: soft_cryp_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: CPU内置安全功能提供编程接口供应用程序调用,验证SM4-ECB加解密 +# 前置条件: +# yum install openssl-devel -y +# 步骤: +# 1. 对soft_cryp_tc001.cfg文件内容进行SM4-ECB加密,核对加密前后内容 +# 2. 对加密文件进行解密,核对解密后内容与原文一致 +# 预期结果: +# 1. 加密成功,加密后内容与原内容不一致 +# 2. 解密成功,解密后内容与原内容一致 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=("SM4-ECB软件加解密功能验证") +tcnt=${#TCASE_DESC[@]} + +setup() +{ + if [ ! -x ./soft_cryp_tc001 ]; then + tst_brk $TCONF "soft_cryp_tc001 binary not found" + fi + + if [ ! -f ./soft_cryp_tc001.cfg ]; then + tst_brk $TCONF "soft_cryp_tc001.cfg config file not found" + fi +} + +cleanup() +{ + rm -f sf_encrypted.txt sf_decrypted.txt sm4key.txt +} + +run_test() +{ + local n=$1 + + ./soft_cryp_tc001 + local ret=$? + if [ $ret -eq 0 ]; then + tst_res $TPASS "soft_cryp_tc001 SM4-ECB encrypt/decrypt all tests passed" + else + tst_res $TFAIL "soft_cryp_tc001 failed with exit code $ret" + fi +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/cpu/taskset_process_cpu_tc001.sh b/os_smoke_src/testcases/kernel/cpu/taskset_process_cpu_tc001.sh new file mode 100755 index 0000000..b48a8fc --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/taskset_process_cpu_tc001.sh @@ -0,0 +1,82 @@ +#!/bin/bash +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: taskset_process_cpu_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 多核CPU支持线程绑定,允许限制某个应用运行在指定核心上 +# 前置条件: yum install -y util-linux stress-ng procps-ng +# 步骤: +# 1. 启动负载进程,记录某个进程的pid和所在核 +# 2. 使用taskset绑定该进程到另一个核 +# 3. 检查CPU亲和性和实际运行核是否为目标核 +# 预期结果: +# 1. 负载进程启动成功 +# 2. taskset设置成功 +# 3. 进程迁移到指定核运行 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=("taskset单核绑定验证") +tcnt=${#TCASE_DESC[@]} + +setup() +{ + killall stress-ng 2>/dev/null || true +} + +cleanup() +{ + killall stress-ng 2>/dev/null || true + rm -rf $0.log* 2>/dev/null +} + +run_test() +{ + local n=$1 + + local cpu_num=$(cat /proc/cpuinfo | grep processor | wc -l) + local stress_num=$((cpu_num / 4)) + [ $stress_num -lt 1 ] && stress_num=1 + + stress-ng --cpu $stress_num --cpu-load 50 & + sleep 10 + + ps -eo pid,psr,args | grep stress-ng | grep -v grep > $0.log1 2>&1 + local pid1=$(cat $0.log1 | tail -n 1 | awk '{print $1}') + local psr1=$(cat $0.log1 | tail -n 1 | awk '{print $2}') + tst_res $TINFO "tracked pid=$pid1 on cpu=$psr1" + + local psr + if [ "$psr1" -eq $((cpu_num - 1)) ]; then + psr=$((psr1 - 1)) + else + psr=$((psr1 + 1)) + fi + + taskset -apc $psr $pid1 > /dev/null 2>&1 + sleep 3 + + local cpu_affinity=$(cat /proc/$pid1/status | grep Cpus_allowed_list | awk '{print $2}') + if [ "$psr" -ne "$cpu_affinity" ]; then + tst_res $TFAIL "affinity check failed: expected=$psr got=$cpu_affinity" + return + fi + + ps -eo pid,psr,args | grep stress-ng | grep -v grep > $0.log2 2>&1 + local psr2=$(cat $0.log2 | grep "$pid1 " | awk '{print $2}') + if [ "$psr" -ne "$psr2" ]; then + tst_res $TFAIL "taskset failed: expected cpu=$psr actual cpu=$psr2" + else + tst_res $TPASS "taskset single-core binding verified: pid=$pid1 on cpu=$psr" + fi +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/cpu/taskset_process_cpus_tc001.sh b/os_smoke_src/testcases/kernel/cpu/taskset_process_cpus_tc001.sh new file mode 100755 index 0000000..f822b10 --- /dev/null +++ b/os_smoke_src/testcases/kernel/cpu/taskset_process_cpus_tc001.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: taskset_process_cpus_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 多核CPU提供系统访问接口,应用通过访问接口获取运行状态和控制多核调度 +# 前置条件: yum install -y util-linux stress-ng procps-ng +# 步骤: +# 1. 启动负载进程,taskset绑定到单核0 +# 2. 验证进程运行在核0 +# 3. taskset绑定到核范围1-3 +# 4. 验证进程运行在核1-3范围内 +# 预期结果: +# 1. 绑定单核成功 +# 2. 进程运行在核0 +# 3. 绑定核范围成功 +# 4. 进程运行在核1-3范围内 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "taskset绑定单核验证" + "taskset绑定核范围验证" +) +tcnt=${#TCASE_DESC[@]} + +STRESS_PID="" + +setup() +{ + killall stress-ng 2>/dev/null || true + local cpu_num=$(cat /proc/cpuinfo | grep processor | wc -l) + local stress_num=$((cpu_num / 4)) + [ $stress_num -lt 1 ] && stress_num=1 + stress-ng --cpu $stress_num --cpu-load 50 & + sleep 10 + ps -eo pid,psr,args | grep stress-ng | grep -v grep > $0.log1 2>&1 + STRESS_PID=$(cat $0.log1 | tail -n 1 | awk '{print $1}') + tst_res $TINFO "tracked stress pid=$STRESS_PID" +} + +cleanup() +{ + killall stress-ng 2>/dev/null || true + rm -rf $0.log* 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + taskset -apc 0 $STRESS_PID > /dev/null 2>&1 + sleep 3 + local cpu_affinity=$(cat /proc/$STRESS_PID/status | grep Cpus_allowed_list | awk '{print $2}') + if [ "$cpu_affinity" != "0" ]; then + tst_res $TFAIL "affinity check failed: expected=0 got=$cpu_affinity" + return + fi + local psr=$(ps -eo pid,psr,args | grep stress-ng | grep -v grep | grep "$STRESS_PID " | awk '{print $2}') + if [ "$psr" -ne 0 ]; then + tst_res $TFAIL "taskset single-core failed: expected cpu=0 actual=$psr" + else + tst_res $TPASS "taskset single-core 0 binding verified" + fi + ;; + 1) + taskset -apc 1-3 $STRESS_PID > /dev/null 2>&1 + sleep 3 + local cpu_affinity=$(cat /proc/$STRESS_PID/status | grep Cpus_allowed_list | awk '{print $2}') + if [ "$cpu_affinity" != "1-3" ]; then + tst_res $TFAIL "affinity check failed: expected=1-3 got=$cpu_affinity" + return + fi + local ok=1 + for i in $(seq 1 5); do + local psr=$(ps -eo pid,psr,args | grep stress-ng | grep -v grep | grep "$STRESS_PID " | awk '{print $2}') + if [ "$psr" -lt 1 ] || [ "$psr" -gt 3 ]; then + tst_res $TFAIL "taskset range failed: cpu=$psr not in 1-3" + ok=0 + break + fi + sleep 2 + done + [ $ok -eq 1 ] && tst_res $TPASS "taskset range 1-3 binding verified" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/fs/Makefile b/os_smoke_src/testcases/kernel/fs/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/kernel/fs/block_vfs_ext4_tc001.sh b/os_smoke_src/testcases/kernel/fs/block_vfs_ext4_tc001.sh new file mode 100755 index 0000000..b7034d3 --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/block_vfs_ext4_tc001.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: block_vfs_ext4_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 虚拟文件系统测试,支持将存储设备抽象为统一的文件操作接口 +# 前置条件: 无 +# 步骤: +# 1. dd创建1G文件,格式化为ext4 +# 2. 挂载后进行读写删除操作 +# 3. 卸载并清理 +# 预期结果: +# 1. 格式化成功 +# 2. 读写删除操作成功 +# 3. 卸载清理成功 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=("VFS ext4文件操作验证") +tcnt=${#TCASE_DESC[@]} + +MOUNT_DIR="/data/fstest" + +setup() +{ + return 0 +} + +cleanup() +{ + umount $MOUNT_DIR 2>/dev/null || true + rm -rf $MOUNT_DIR /tmp/fstest 2>/dev/null +} + +run_test() +{ + local n=$1 + dd if=/dev/zero of=/tmp/fstest bs=1M count=1024 2>/dev/null + local free_device=/tmp/fstest + mkdir -p $MOUNT_DIR + + echo y | mkfs.ext4 $free_device > /dev/null 2>&1 + if [ $? -ne 0 ]; then tst_res $TFAIL "mkfs.ext4 failed"; return; fi + + mount $free_device $MOUNT_DIR + if [ $? -ne 0 ]; then tst_res $TFAIL "mount failed"; return; fi + + cd $MOUNT_DIR || { tst_res $TFAIL "cd into mount dir failed"; return; } + echo xx > x || { tst_res $TFAIL "write file failed"; cd -; return; } + cat x | grep -q "xx" || { tst_res $TFAIL "read file failed"; cd -; return; } + rm -rf x || { tst_res $TFAIL "rm file failed"; cd -; return; } + cd - + + umount $MOUNT_DIR || { tst_res $TFAIL "umount failed"; return; } + tst_res $TPASS "VFS ext4 create/read/write/delete verified" + rm -rf $MOUNT_DIR /tmp/fstest +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/fs/ext2_tc001.sh b/os_smoke_src/testcases/kernel/fs/ext2_tc001.sh new file mode 100755 index 0000000..6a9cb04 --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/ext2_tc001.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ext2_tc001 +# 用例类型: 功能用例 +# 优先级: P1 +# 用例描述: ext2文件系统格式化、挂载、读写、卸载功能验证 +# 前置条件: 需要可用的磁盘设备或支持loop设备 +# 步骤: +# 1. 格式化设备为ext2并挂载,创建文件读写验证 +# 2. 卸载并清理设备 +# 预期结果: +# 1. ext2格式化成功,挂载成功,读写正常 +# 2. 卸载成功,清理完成 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "ext2格式化挂载与读写" + "ext2卸载与清理" +) +tcnt=${#TCASE_DESC[@]} + +DISK_NAME="" +MOUNT_DIR="/data/fstest" +USE_LOOP=0 + +setup() +{ + losetup -D 2>/dev/null || true + if [ -z "${CONTROL_CASE_DISK_DEVICE}" ]; then + DISK_NAME=$(losetup -f) + dd if=/dev/zero of=loop.img bs=10M count=10 2>/dev/null + losetup -f loop.img || tst_brk $TFAIL "losetup failed" + USE_LOOP=1 + else + DISK_NAME=${CONTROL_CASE_DISK_DEVICE} + fi +} + +cleanup() +{ + umount ${MOUNT_DIR} 2>/dev/null || true + rm -rf ${MOUNT_DIR} 2>/dev/null || true + if [ $USE_LOOP -eq 1 ]; then + losetup -d $DISK_NAME 2>/dev/null || true + rm -f loop.img 2>/dev/null || true + fi + losetup -D 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + case $n in + 0) + test -d ${MOUNT_DIR} && rm -rf ${MOUNT_DIR}/* || mkdir -p ${MOUNT_DIR} + echo y | mkfs.ext2 $DISK_NAME > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "mkfs.ext2 $DISK_NAME failed" + return + fi + mount $DISK_NAME ${MOUNT_DIR} || { tst_res $TFAIL "mount ext2 failed"; return; } + cd $MOUNT_DIR + mkdir test_dir || { tst_res $TFAIL "mkdir in ext2 failed"; cd -; return; } + echo "xxx" > test_dir/test_file || { tst_res $TFAIL "write in ext2 failed"; cd -; return; } + grep -q "xxx" test_dir/test_file || { tst_res $TFAIL "read in ext2 failed"; cd -; return; } + rm -rf test_dir + cd - + tst_res $TPASS "ext2 format, mount, read/write verified" + ;; + 1) + umount ${MOUNT_DIR} + if [ $? -ne 0 ]; then + tst_res $TFAIL "umount ext2 failed" + else + tst_res $TPASS "ext2 unmounted successfully" + fi + rm -rf $MOUNT_DIR + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/fs/ext3_tc001.sh b/os_smoke_src/testcases/kernel/fs/ext3_tc001.sh new file mode 100755 index 0000000..3382792 --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/ext3_tc001.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ext3_tc001 +# 用例类型: 功能用例 +# 优先级: P1 +# 用例描述: ext3文件系统格式化、挂载、读写、卸载功能验证 +# 前置条件: 需要可用的磁盘设备或支持loop设备 +# 步骤: +# 1. 格式化设备为ext3并挂载,创建文件读写验证 +# 2. 卸载并清理设备 +# 预期结果: +# 1. ext3格式化成功,挂载成功,读写正常 +# 2. 卸载成功,清理完成 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "ext3格式化挂载与读写" + "ext3卸载与清理" +) +tcnt=${#TCASE_DESC[@]} + +DISK_NAME="" +MOUNT_DIR="/data/fstest" +USE_LOOP=0 + +setup() +{ + losetup -D 2>/dev/null || true + if [ -z "${CONTROL_CASE_DISK_DEVICE}" ]; then + DISK_NAME=$(losetup -f) + dd if=/dev/zero of=loop.img bs=10M count=10 2>/dev/null + losetup -f loop.img || tst_brk $TFAIL "losetup failed" + USE_LOOP=1 + else + DISK_NAME=${CONTROL_CASE_DISK_DEVICE} + fi +} + +cleanup() +{ + umount ${MOUNT_DIR} 2>/dev/null || true + rm -rf ${MOUNT_DIR} 2>/dev/null || true + if [ $USE_LOOP -eq 1 ]; then + losetup -d $DISK_NAME 2>/dev/null || true + rm -f loop.img 2>/dev/null || true + fi + losetup -D 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + case $n in + 0) + test -d ${MOUNT_DIR} && rm -rf ${MOUNT_DIR}/* || mkdir -p ${MOUNT_DIR} + echo y | mkfs.ext3 $DISK_NAME > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "mkfs.ext3 $DISK_NAME failed" + return + fi + mount $DISK_NAME ${MOUNT_DIR} || { tst_res $TFAIL "mount ext3 failed"; return; } + cd $MOUNT_DIR + mkdir test_dir || { tst_res $TFAIL "mkdir in ext3 failed"; cd -; return; } + echo "xxx" > test_dir/test_file || { tst_res $TFAIL "write in ext3 failed"; cd -; return; } + grep -q "xxx" test_dir/test_file || { tst_res $TFAIL "read in ext3 failed"; cd -; return; } + rm -rf test_dir + cd - + tst_res $TPASS "ext3 format, mount, read/write verified" + ;; + 1) + umount ${MOUNT_DIR} + if [ $? -ne 0 ]; then + tst_res $TFAIL "umount ext3 failed" + else + tst_res $TPASS "ext3 unmounted successfully" + fi + rm -rf $MOUNT_DIR + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/fs/ext4_tc001.sh b/os_smoke_src/testcases/kernel/fs/ext4_tc001.sh new file mode 100755 index 0000000..b5d7591 --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/ext4_tc001.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ext4_tc001 +# 用例类型: 功能用例 +# 优先级: P1 +# 用例描述: ext4文件系统格式化、挂载、读写、卸载功能验证 +# 前置条件: 需要可用的磁盘设备或支持loop设备 +# 步骤: +# 1. 格式化设备为ext4并挂载,创建文件读写验证 +# 2. 卸载并清理设备 +# 预期结果: +# 1. ext4格式化成功,挂载成功,读写正常 +# 2. 卸载成功,清理完成 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "ext4格式化挂载与读写" + "ext4卸载与清理" +) +tcnt=${#TCASE_DESC[@]} + +DISK_NAME="" +MOUNT_DIR="/data/fstest" +USE_LOOP=0 + +setup() +{ + losetup -D 2>/dev/null || true + if [ -z "${CONTROL_CASE_DISK_DEVICE}" ]; then + DISK_NAME=$(losetup -f) + dd if=/dev/zero of=loop.img bs=10M count=10 2>/dev/null + losetup -f loop.img || tst_brk $TFAIL "losetup failed" + USE_LOOP=1 + else + DISK_NAME=${CONTROL_CASE_DISK_DEVICE} + fi +} + +cleanup() +{ + umount ${MOUNT_DIR} 2>/dev/null || true + rm -rf ${MOUNT_DIR} 2>/dev/null || true + if [ $USE_LOOP -eq 1 ]; then + losetup -d $DISK_NAME 2>/dev/null || true + rm -f loop.img 2>/dev/null || true + fi + losetup -D 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + case $n in + 0) + test -d ${MOUNT_DIR} && rm -rf ${MOUNT_DIR}/* || mkdir -p ${MOUNT_DIR} + echo y | mkfs.ext4 $DISK_NAME > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "mkfs.ext4 $DISK_NAME failed" + return + fi + mount $DISK_NAME ${MOUNT_DIR} || { tst_res $TFAIL "mount ext4 failed"; return; } + cd $MOUNT_DIR + mkdir test_dir || { tst_res $TFAIL "mkdir in ext4 failed"; cd -; return; } + echo "xxx" > test_dir/test_file || { tst_res $TFAIL "write in ext4 failed"; cd -; return; } + grep -q "xxx" test_dir/test_file || { tst_res $TFAIL "read in ext4 failed"; cd -; return; } + rm -rf test_dir + cd - + tst_res $TPASS "ext4 format, mount, read/write verified" + ;; + 1) + umount ${MOUNT_DIR} + if [ $? -ne 0 ]; then + tst_res $TFAIL "umount ext4 failed" + else + tst_res $TPASS "ext4 unmounted successfully" + fi + rm -rf $MOUNT_DIR + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/fs/fat32_tc001.sh b/os_smoke_src/testcases/kernel/fs/fat32_tc001.sh new file mode 100755 index 0000000..be3ac8d --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/fat32_tc001.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: fat32_tc001 +# 用例类型: 功能用例 +# 优先级: P1 +# 用例描述: FAT32文件系统格式化、挂载、读写、卸载功能验证 +# 前置条件: 需要可用的磁盘设备或支持loop设备 +# 步骤: +# 1. 格式化设备为fat32并挂载,创建文件读写验证 +# 2. 卸载并清理设备 +# 预期结果: +# 1. fat32格式化成功,挂载成功,读写正常 +# 2. 卸载成功,清理完成 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "FAT32格式化挂载与读写" + "FAT32卸载与清理" +) +tcnt=${#TCASE_DESC[@]} + +DISK_NAME="" +MOUNT_DIR="/data/fstest" +USE_LOOP=0 + +setup() +{ + losetup -D 2>/dev/null || true + if [ -z "${CONTROL_CASE_DISK_DEVICE}" ]; then + DISK_NAME=$(losetup -f) + dd if=/dev/zero of=loop.img bs=10M count=10 2>/dev/null + losetup -f loop.img || tst_brk $TFAIL "losetup failed" + USE_LOOP=1 + else + DISK_NAME=${CONTROL_CASE_DISK_DEVICE} + fi +} + +cleanup() +{ + umount ${MOUNT_DIR} 2>/dev/null || true + rm -rf ${MOUNT_DIR} 2>/dev/null || true + if [ $USE_LOOP -eq 1 ]; then + losetup -d $DISK_NAME 2>/dev/null || true + rm -f loop.img 2>/dev/null || true + fi + losetup -D 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + case $n in + 0) + test -d ${MOUNT_DIR} && rm -rf ${MOUNT_DIR}/* || mkdir -p ${MOUNT_DIR} + echo y | mkfs.fat -F 32 $DISK_NAME > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "mkfs.fat -F 32 $DISK_NAME failed" + return + fi + mount $DISK_NAME ${MOUNT_DIR} || { tst_res $TFAIL "mount fat32 failed"; return; } + cd $MOUNT_DIR + mkdir test_dir || { tst_res $TFAIL "mkdir in fat32 failed"; cd -; return; } + echo "xxx" > test_dir/test_file || { tst_res $TFAIL "write in fat32 failed"; cd -; return; } + grep -q "xxx" test_dir/test_file || { tst_res $TFAIL "read in fat32 failed"; cd -; return; } + rm -rf test_dir + cd - + tst_res $TPASS "FAT32 format, mount, read/write verified" + ;; + 1) + umount ${MOUNT_DIR} + if [ $? -ne 0 ]; then + tst_res $TFAIL "umount fat32 failed" + else + tst_res $TPASS "FAT32 unmounted successfully" + fi + rm -rf $MOUNT_DIR + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/fs/fdisk_delete.ini b/os_smoke_src/testcases/kernel/fs/fdisk_delete.ini new file mode 100755 index 0000000..73d7e99 --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/fdisk_delete.ini @@ -0,0 +1,14 @@ +d + + +d + + +d + + +d + + +w + diff --git a/os_smoke_src/testcases/kernel/fs/fdisk_gpt_part.ini b/os_smoke_src/testcases/kernel/fs/fdisk_gpt_part.ini new file mode 100755 index 0000000..9ad890a --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/fdisk_gpt_part.ini @@ -0,0 +1,8 @@ +p + +g + +p + +w + diff --git a/os_smoke_src/testcases/kernel/fs/fdisk_new_part.ini b/os_smoke_src/testcases/kernel/fs/fdisk_new_part.ini new file mode 100755 index 0000000..2bee045 --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/fdisk_new_part.ini @@ -0,0 +1,13 @@ +n +p + + +52428800 + +n +p + + + +w + diff --git a/os_smoke_src/testcases/kernel/fs/fs_save_search_tc001.sh b/os_smoke_src/testcases/kernel/fs/fs_save_search_tc001.sh new file mode 100755 index 0000000..1e69274 --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/fs_save_search_tc001.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: fs_save_search_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 应支持文件存储、检索和共享 +# 前置条件: 无 +# 步骤: +# 1. 创建临时目录,写入文件 +# 2. 退出目录后使用find检索文件 +# 3. 在外部读取文件内容验证 +# 4. 删除临时目录 +# 预期结果: +# 1. 文件创建写入成功 +# 2. find检索到文件 +# 3. 文件内容正确 +# 4. 删除成功 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=("文件存储检索验证") +tcnt=${#TCASE_DESC[@]} + +TEST_DIR="/tmp/save_search_test_dir" + +setup() +{ + rm -rf $TEST_DIR 2>/dev/null +} + +cleanup() +{ + rm -rf $TEST_DIR 2>/dev/null +} + +run_test() +{ + local n=$1 + mkdir -p $TEST_DIR + cd $TEST_DIR || { tst_res $TFAIL "cd into test dir failed"; return; } + echo xx > x || { tst_res $TFAIL "write file failed"; cd -; return; } + cat x | grep -q "xx" || { tst_res $TFAIL "save/read file failed"; cd -; return; } + cd - + + find $TEST_DIR -name x | grep -q "$TEST_DIR/x" + if [ $? -ne 0 ]; then tst_res $TFAIL "find search failed"; return; fi + + cat $TEST_DIR/x | grep -q "xx" + if [ $? -ne 0 ]; then tst_res $TFAIL "external read failed"; return; fi + + tst_res $TPASS "file save, search and read verified" + rm -rf $TEST_DIR +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/fs/longest_filename_tc001.sh b/os_smoke_src/testcases/kernel/fs/longest_filename_tc001.sh new file mode 100755 index 0000000..8b3a80b --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/longest_filename_tc001.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: longest_filename_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 最大文件名长度不小于255字节 +# 前置条件: 无 +# 步骤: +# 1. 创建一个文件名255字节的文件并进行读写验证 +# 预期结果: +# 1. 创建成功,读写正常 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "255字节文件名创建与读写验证" +) +tcnt=${#TCASE_DESC[@]} + +LONG_FILENAME="" + +setup() +{ + LONG_FILENAME="" + for i in $(seq 1 255); do + LONG_FILENAME="a${LONG_FILENAME}" + done +} + +cleanup() +{ + [ -n "$LONG_FILENAME" ] && rm -f "$LONG_FILENAME" 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + local name_len + name_len=$(echo -n "$LONG_FILENAME" | wc -c) + if [ "$name_len" -ne 255 ]; then + tst_res $TFAIL "filename length is $name_len, expected 255" + return + fi + + touch "$LONG_FILENAME" || { tst_res $TFAIL "create 255-byte filename failed"; return; } + echo xx > "$LONG_FILENAME" || { tst_res $TFAIL "write to 255-byte filename failed"; return; } + grep -q xx "$LONG_FILENAME" || { tst_res $TFAIL "read from 255-byte filename failed"; return; } + rm -f "$LONG_FILENAME" || { tst_res $TFAIL "remove 255-byte filename failed"; return; } + + tst_res $TPASS "255-byte filename create, write, read, remove verified" +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/fs/ntfs_tc001.sh b/os_smoke_src/testcases/kernel/fs/ntfs_tc001.sh new file mode 100755 index 0000000..d9c44e4 --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/ntfs_tc001.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ntfs_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: NTFS文件系统格式化、挂载、读写、卸载功能验证 +# 前置条件: yum install ntfsprogs -y,需要block device +# 步骤: +# 1. 在空闲磁盘上创建分区并格式化为NTFS +# 2. 挂载分区并进行文件读写验证 +# 3. 卸载分区并删除分区清理 +# 预期结果: +# 1. 创建分区和NTFS格式化成功 +# 2. 文件读写正常 +# 3. 卸载和删除分区成功 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +source ../../../../control + +TCASE_DESC=( + "NTFS分区创建与格式化挂载读写" +) +tcnt=${#TCASE_DESC[@]} + +FREE_DEVICE="" +TEST_DEVICE="" +MOUNT_DIR="/data/fstest" + +setup() +{ + ensure_command "mkfs.ntfs" "ntfsprogs" + if [ -z "${CONTROL_CASE_DISK_DEVICE}" ]; then + tst_brk $TCONF "need a disk in control config CONTROL_CASE_DISK_DEVICE for this case" + fi + + for disk_name in ${CONTROL_CASE_DISK_DEVICE}; do + if lsblk | grep disk | grep -v sda | grep -v vda | grep -v lvm | grep -v NAME | awk '{print "/dev/"$1}' | grep -q "$disk_name"; then + FREE_DEVICE=$disk_name + break + fi + done + + if [ -z "${FREE_DEVICE}" ]; then + tst_brk $TCONF "need a free disk in control config CONTROL_CASE_DISK_DEVICE" + fi +} + +cleanup() +{ + umount ${MOUNT_DIR} 2>/dev/null || true + rm -rf ${MOUNT_DIR} 2>/dev/null || true + [ -n "$FREE_DEVICE" ] && fdisk $FREE_DEVICE < fdisk_delete.ini 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + fdisk $FREE_DEVICE < fdisk_new_part.ini || { tst_res $TFAIL "fdisk create partition failed"; return; } + sleep 3 + + if echo "$FREE_DEVICE" | grep -q "nvme"; then + TEST_DEVICE=${FREE_DEVICE}p1 + else + TEST_DEVICE=${FREE_DEVICE}1 + fi + + mkdir -p $MOUNT_DIR + echo y | mkfs.ntfs -Q $TEST_DEVICE || { tst_res $TFAIL "mkfs.ntfs $TEST_DEVICE fail"; return; } + mount $TEST_DEVICE ${MOUNT_DIR} || { tst_res $TFAIL "mount ntfs failed"; return; } + + cd $MOUNT_DIR + echo xx > x || { tst_res $TFAIL "write file fail"; cd -; return; } + cat x | grep -q "xx" || { tst_res $TFAIL "read file fail"; cd -; return; } + rm -rf x + cd - + + umount ${MOUNT_DIR} || { tst_res $TFAIL "umount ntfs failed"; return; } + fdisk $FREE_DEVICE < fdisk_delete.ini || { tst_res $TFAIL "fdisk delete partition failed"; return; } + rm -rf $MOUNT_DIR + + tst_res $TPASS "NTFS partition create, format, mount, read/write, umount, delete verified" +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/fs/vfat_tc001.sh b/os_smoke_src/testcases/kernel/fs/vfat_tc001.sh new file mode 100755 index 0000000..e323fba --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/vfat_tc001.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: vfat_tc001 +# 用例类型: 功能用例 +# 优先级: P1 +# 用例描述: vfat文件系统格式化、挂载、读写、卸载功能验证 +# 前置条件: 需要可用的磁盘设备或支持loop设备 +# 步骤: +# 1. 格式化设备为vfat并挂载,创建文件读写验证 +# 2. 卸载并清理设备 +# 预期结果: +# 1. vfat格式化成功,挂载成功,读写正常 +# 2. 卸载成功,清理完成 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "vfat格式化挂载与读写" + "vfat卸载与清理" +) +tcnt=${#TCASE_DESC[@]} + +DISK_NAME="" +MOUNT_DIR="/data/fstest" +USE_LOOP=0 + +setup() +{ + losetup -D 2>/dev/null || true + if [ -z "${CONTROL_CASE_DISK_DEVICE}" ]; then + DISK_NAME=$(losetup -f) + dd if=/dev/zero of=loop.img bs=10M count=10 2>/dev/null + losetup -f loop.img || tst_brk $TFAIL "losetup failed" + USE_LOOP=1 + else + DISK_NAME=${CONTROL_CASE_DISK_DEVICE} + fi +} + +cleanup() +{ + umount ${MOUNT_DIR} 2>/dev/null || true + rm -rf ${MOUNT_DIR} 2>/dev/null || true + if [ $USE_LOOP -eq 1 ]; then + losetup -d $DISK_NAME 2>/dev/null || true + rm -f loop.img 2>/dev/null || true + fi + losetup -D 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + case $n in + 0) + test -d ${MOUNT_DIR} && rm -rf ${MOUNT_DIR}/* || mkdir -p ${MOUNT_DIR} + echo y | mkfs.vfat $DISK_NAME > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "mkfs.vfat $DISK_NAME failed" + return + fi + mount $DISK_NAME ${MOUNT_DIR} || { tst_res $TFAIL "mount vfat failed"; return; } + cd $MOUNT_DIR + mkdir test_dir || { tst_res $TFAIL "mkdir in vfat failed"; cd -; return; } + echo "xxx" > test_dir/test_file || { tst_res $TFAIL "write in vfat failed"; cd -; return; } + grep -q "xxx" test_dir/test_file || { tst_res $TFAIL "read in vfat failed"; cd -; return; } + rm -rf test_dir + cd - + tst_res $TPASS "vfat format, mount, read/write verified" + ;; + 1) + umount ${MOUNT_DIR} + if [ $? -ne 0 ]; then + tst_res $TFAIL "umount vfat failed" + else + tst_res $TPASS "vfat unmounted successfully" + fi + rm -rf $MOUNT_DIR + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/fs/vfs_stdin_stdout_tc001.sh b/os_smoke_src/testcases/kernel/fs/vfs_stdin_stdout_tc001.sh new file mode 100755 index 0000000..ac682f3 --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/vfs_stdin_stdout_tc001.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: vfs_stdin_stdout_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: VFS标准输入输出设备测试 +# 前置条件: 无 +# 步骤: +# 1. 向/dev/stdin写数据并重定向到日志文件 +# 2. 向/dev/stdout写数据并重定向到日志文件 +# 3. 验证日志文件内容正确 +# 预期结果: +# 1. stdin写入成功 +# 2. stdout写入成功 +# 3. 文件内容正确 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "stdin设备写入验证" + "stdout设备写入验证" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + rm -rf intest.log outtest.log 2>/dev/null +} + +cleanup() +{ + rm -rf intest.log outtest.log 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + echo "in" | tee intest.log > /dev/null 2>&1 + if cat intest.log | grep -q "in"; then + tst_res $TPASS "stdin device write/read verified" + else + tst_res $TFAIL "stdin device test failed" + fi + ;; + 1) + echo "out" | tee outtest.log > /dev/null 2>&1 + if cat outtest.log | grep -q "out"; then + tst_res $TPASS "stdout device write/read verified" + else + tst_res $TFAIL "stdout device test failed" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/fs/xfs_tc001.sh b/os_smoke_src/testcases/kernel/fs/xfs_tc001.sh new file mode 100755 index 0000000..b70a690 --- /dev/null +++ b/os_smoke_src/testcases/kernel/fs/xfs_tc001.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: xfs_tc001 +# 用例类型: 功能用例 +# 优先级: P1 +# 用例描述: xfs文件系统格式化、挂载、读写、卸载功能验证 +# 前置条件: 需要可用的磁盘设备或支持loop设备 +# 步骤: +# 1. 格式化设备为xfs并挂载,创建文件读写验证 +# 2. 卸载并清理设备 +# 预期结果: +# 1. xfs格式化成功,挂载成功,读写正常 +# 2. 卸载成功,清理完成 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "xfs格式化挂载与读写" + "xfs卸载与清理" +) +tcnt=${#TCASE_DESC[@]} + +DISK_NAME="" +MOUNT_DIR="/data/fstest" +USE_LOOP=0 + +setup() +{ + ensure_command "mkfs.xfs" "xfsprogs" + losetup -D 2>/dev/null || true + if [ -z "${CONTROL_CASE_DISK_DEVICE}" ]; then + DISK_NAME=$(losetup -f) + # xfs 要求分区至少大于 300MB + dd if=/dev/zero of=loop.img bs=100M count=4 2>/dev/null + losetup -f loop.img || tst_brk $TFAIL "losetup failed" + USE_LOOP=1 + else + DISK_NAME=${CONTROL_CASE_DISK_DEVICE} + fi +} + +cleanup() +{ + umount ${MOUNT_DIR} 2>/dev/null || true + rm -rf ${MOUNT_DIR} 2>/dev/null || true + if [ $USE_LOOP -eq 1 ]; then + losetup -d $DISK_NAME 2>/dev/null || true + rm -f loop.img 2>/dev/null || true + fi + losetup -D 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + case $n in + 0) + test -d ${MOUNT_DIR} && rm -rf ${MOUNT_DIR}/* || mkdir -p ${MOUNT_DIR} + echo y | mkfs.xfs -f $DISK_NAME > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "mkfs.xfs $DISK_NAME failed" + return + fi + mount $DISK_NAME ${MOUNT_DIR} || { tst_res $TFAIL "mount xfs failed"; return; } + cd $MOUNT_DIR + mkdir test_dir || { tst_res $TFAIL "mkdir in xfs failed"; cd -; return; } + echo "xxx" > test_dir/test_file || { tst_res $TFAIL "write in xfs failed"; cd -; return; } + grep -q "xxx" test_dir/test_file || { tst_res $TFAIL "read in xfs failed"; cd -; return; } + rm -rf test_dir + cd - + tst_res $TPASS "xfs format, mount, read/write verified" + ;; + 1) + umount ${MOUNT_DIR} + if [ $? -ne 0 ]; then + tst_res $TFAIL "umount xfs failed" + else + tst_res $TPASS "xfs unmounted successfully" + fi + rm -rf $MOUNT_DIR + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/mem/Makefile b/os_smoke_src/testcases/kernel/mem/Makefile new file mode 100755 index 0000000..d5a4cd7 --- /dev/null +++ b/os_smoke_src/testcases/kernel/mem/Makefile @@ -0,0 +1,45 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) +ifneq ($(CASES_BIN),) + cp -avf $(CASES_BIN) $(INSTALL_DIR) +endif +ifneq ($(CASES_SH),) + cp -avf $(CASES_SH) $(INSTALL_DIR) +endif +ifneq ($(CASES_PY),) + cp -avf $(CASES_PY) $(INSTALL_DIR) +endif + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif + diff --git a/os_smoke_src/testcases/kernel/mem/huge_page_tc001.c b/os_smoke_src/testcases/kernel/mem/huge_page_tc001.c new file mode 100755 index 0000000..bc5871d --- /dev/null +++ b/os_smoke_src/testcases/kernel/mem/huge_page_tc001.c @@ -0,0 +1,137 @@ +/*************************************************# + * SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (c) 2026 Tencent. All Rights Reserved. + * Author: kevinzcheng + * Date: 2026/03/11 + +# 用例名称:huge_page +# 描述: 应支持内存大页管理,允许应用申请内存大页降低页表转换,优化性能 +# 等级: P0 +# 用例类型: 功能测试 +# 前置条件: +# NA +# 步骤: +# 1,使用mmap MAP_HUGETLB申请一块大页内存 +# 2,写入固定模式并逐字节校验 +# 预期结果: +# 1,mmap映射大页成功 +# 2,数据写入与校验一致,munmap释放成功 +#**************************************************# +*/ + +#include +#include +#include +#include +#include +#include + +#include "../../../../tools/case_lib.c" + +#ifndef MAP_HUGETLB +#define MAP_HUGETLB 0x40000 +#endif + +unsigned int tcnt = 2; + +static size_t length; +static void *mapped_addr; + +static size_t get_default_hugepage_size(void) +{ + FILE *fp = fopen("/proc/meminfo", "r"); + if (!fp) + return 0; + + char line[256]; + size_t size_kb = 0; + while (fgets(line, sizeof(line), fp)) { + if (sscanf(line, "Hugepagesize: %zu kB", &size_kb) == 1) + break; + } + fclose(fp); + return size_kb * 1024U; +} + +static void setup(void) +{ + mapped_addr = MAP_FAILED; + length = get_default_hugepage_size(); + if (length == 0) { + tst_res(TINFO, "cannot read Hugepagesize from /proc/meminfo, defaulting to 2MB"); + length = 2U * 1024U * 1024U; + } + + tst_res(TINFO, "using default hugepage, mapping length: %zu bytes", length); +} + +static void cleanup(void) +{ + if (mapped_addr != MAP_FAILED) { + munmap(mapped_addr, length); + mapped_addr = MAP_FAILED; + } +} + +static void run_test(unsigned int n) +{ + switch (n) { + case 0: { + /* 步骤1: mmap 申请大页内存 */ + mapped_addr = mmap(NULL, length, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, + -1, 0); + if (mapped_addr == MAP_FAILED) { + tst_res(TFAIL, "mmap(MAP_HUGETLB, %zu bytes) failed: %s", + length, strerror(errno)); + return; + } + + tst_res(TPASS, "mmap MAP_HUGETLB %zu bytes at %p succeeded", length, mapped_addr); + break; + } + case 1: { + /* 步骤2: 写入并校验数据 */ + if (mapped_addr == MAP_FAILED) { + tst_res(TFAIL, "skipped: no hugepage mapping available from step 1"); + return; + } + + uint8_t *buf = (uint8_t *)mapped_addr; + size_t i; + + /* 写入递增字节模式 */ + for (i = 0; i < length; i++) + buf[i] = (uint8_t)(i & 0xff); + + /* 校验 */ + int fail = 0; + for (i = 0; i < length; i++) { + if (buf[i] != (uint8_t)(i & 0xff)) { + tst_res(TFAIL, "data mismatch at offset %zu: got 0x%02x, expect 0x%02x", + i, buf[i], (uint8_t)(i & 0xff)); + fail = 1; + break; + } + } + + /* 释放 */ + if (munmap(mapped_addr, length) != 0) { + tst_res(TFAIL, "munmap failed: %s", strerror(errno)); + mapped_addr = MAP_FAILED; + return; + } + mapped_addr = MAP_FAILED; + + if (!fail) + tst_res(TPASS, "hugepage write/verify/munmap all passed (%zu bytes)", length); + break; + } + } +} + +int main(int argc, char *argv[]) +{ + return test_main(argc, argv); +} diff --git a/os_smoke_src/testcases/kernel/mem/huge_page_tc001.sh b/os_smoke_src/testcases/kernel/mem/huge_page_tc001.sh new file mode 100755 index 0000000..ad6b2de --- /dev/null +++ b/os_smoke_src/testcases/kernel/mem/huge_page_tc001.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: huge_page_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 应支持内存大页管理,允许应用申请内存大页降低页表转换,优化性能 +# 前置条件: 无 +# 步骤: +# 1. 设置hugepages数量增加2 +# 2. 运行huge_page_tc001二进制程序映射并读写大页 +# 3. 还原hugepages配置,验证恢复 +# 预期结果: +# 1. 设置大页数成功 +# 2. 映射读写大页成功 +# 3. hugepages配置还原成功 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "大页映射读写验证" + "大页配置还原验证" +) +tcnt=${#TCASE_DESC[@]} + +HUGETLB_DEFAULT_UNIT="" +HUGETLB_DEFAULT_VALUE="" + +setup() +{ + if [ ! -x ./huge_page_tc001 ]; then + tst_brk $TCONF "huge_page_tc001 binary not found" + fi + + HUGETLB_DEFAULT_UNIT=$(grep Hugepagesize /proc/meminfo | awk '{print $2}') + if [ -z "$HUGETLB_DEFAULT_UNIT" ]; then + tst_brk $TCONF "无法获取默认大页大小" + fi + HUGETLB_DEFAULT_VALUE=$(cat /sys/kernel/mm/hugepages/hugepages-${HUGETLB_DEFAULT_UNIT}kB/nr_hugepages) +} + +cleanup() +{ + if [ -n "$HUGETLB_DEFAULT_VALUE" ] && [ -n "$HUGETLB_DEFAULT_UNIT" ]; then + echo $HUGETLB_DEFAULT_VALUE > /sys/kernel/mm/hugepages/hugepages-${HUGETLB_DEFAULT_UNIT}kB/nr_hugepages 2>/dev/null + fi +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local set_pages=$((HUGETLB_DEFAULT_VALUE + 2)) + echo $set_pages > /sys/kernel/mm/hugepages/hugepages-${HUGETLB_DEFAULT_UNIT}kB/nr_hugepages + if [ $? -ne 0 ]; then + tst_res $TFAIL "set hugepages to $set_pages failed" + return + fi + sleep 3 + + local actual=$(cat /sys/kernel/mm/hugepages/hugepages-${HUGETLB_DEFAULT_UNIT}kB/nr_hugepages) + if [ "$actual" -lt "$set_pages" ]; then + tst_brk $TCONF "hugepage alloc insufficient: need $set_pages, got $actual (pagesize=${HUGETLB_DEFAULT_UNIT}kB)" + fi + + ./huge_page_tc001 + local ret=$? + if [ $ret -eq 0 ]; then + tst_res $TPASS "hugepage mapping and read/write verification passed" + elif [ $ret -eq 32 ]; then + tst_res $TCONF "huge_page_tc001 skipped (precondition not met)" + else + tst_res $TFAIL "huge_page_tc001 binary execution failed with exit code $ret" + fi + ;; + 1) + echo $HUGETLB_DEFAULT_VALUE > /sys/kernel/mm/hugepages/hugepages-${HUGETLB_DEFAULT_UNIT}kB/nr_hugepages + sleep 3 + local nr_end=$(cat /sys/kernel/mm/hugepages/hugepages-${HUGETLB_DEFAULT_UNIT}kB/nr_hugepages) + if [ "$nr_end" -eq "$HUGETLB_DEFAULT_VALUE" ]; then + tst_res $TPASS "hugepage configuration restored to $HUGETLB_DEFAULT_VALUE" + else + tst_res $TFAIL "hugepage restore failed: expected=$HUGETLB_DEFAULT_VALUE actual=$nr_end" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/mem/numa_aware_tc001.c b/os_smoke_src/testcases/kernel/mem/numa_aware_tc001.c new file mode 100755 index 0000000..648362a --- /dev/null +++ b/os_smoke_src/testcases/kernel/mem/numa_aware_tc001.c @@ -0,0 +1,25 @@ +/*************************************************# + * SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (c) 2026 Tencent. All Rights Reserved. + * Author: kevinzcheng + * Date: 2026/03/11 +#**************************************************# +*/ +#include +#include +#include +#include + +#define SIZE 16 + +int main(int argc, char **argv){ + int i; + unsigned long *p = malloc(1024 * 1024 * SIZE); + + for(i = 0; i < 1024 * ((1024 * SIZE) / sizeof(long)); i++){ + p[i] = 1; + } + pause(); + return 0; +} + diff --git a/os_smoke_src/testcases/kernel/mem/numa_aware_tc001.sh b/os_smoke_src/testcases/kernel/mem/numa_aware_tc001.sh new file mode 100755 index 0000000..547abf6 --- /dev/null +++ b/os_smoke_src/testcases/kernel/mem/numa_aware_tc001.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: numa_aware_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 应支持NUMA近节点优化 +# 前置条件: yum install -y numactl +# 步骤: +# 1. 获取NUMA节点总数 +# 2. 遍历所有NUMA节点,使用numactl绑定CPU和内存 +# 3. 验证程序使用指定NUMA节点的内存(>=16M)和CPU +# 预期结果: +# 1. 正确获取NUMA节点数 +# 2. 程序使用指定NUMA节点资源 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=("NUMA近节点优化验证") +tcnt=${#TCASE_DESC[@]} + +setup() +{ + ensure_command "numactl" + local numa_num=$(numactl --hardware 2>/dev/null | grep "available: " | awk '{print $2}') + if [ -z "$numa_num" ] || [ "$numa_num" -lt 2 ]; then + tst_brk $TCONF "NUMA节点数不足(需>=2), got ${numa_num:-0}" + fi + + local all_cpus="" + for node in $(seq 0 $((numa_num - 1))); do + local node_cpus=$(lscpu -p=cpu,node | grep -v '^#' | awk -F',' -v n="$node" '$2==n {print $1}') + for cpu in $node_cpus; do + if echo "$all_cpus" | grep -qw "$cpu"; then + tst_brk $TCONF "CPU $cpu shared by multiple NUMA nodes, environment not suitable" + fi + done + all_cpus="$all_cpus $node_cpus" + done +} + +cleanup() +{ + return 0 +} + +run_test() +{ + local n=$1 + local numa_num=$(numactl --hardware | grep "available: " | awk '{print $2}') + local fail=0 + + for node in $(seq 0 $((numa_num - 1))); do + numactl --cpunodebind=$node --membind=$node ./numa_aware_tc001 & + local pid=$! + sleep 1 + local node_awk=$((node + 2)) + local mem_used=$(numastat -p $pid 2>/dev/null | tail -n 1 | awk '{print $'$node_awk'}' | awk -F \. '{print $1}') + if [ -n "$mem_used" ] && [ "$mem_used" -lt 16 ]; then + tst_res $TFAIL "node $node: memory usage ${mem_used}MB < 16MB" + fail=1 + fi + + local cpu=$(cat /proc/$pid/stat 2>/dev/null | awk '{print $39}') + local numa_list=$(lscpu -p=cpu,node | grep -E "^($cpu)," | awk -F ',' '{print $2}' | sort -u) + kill -9 $pid 2>/dev/null + if [ "$numa_list" != "$node" ]; then + tst_res $TFAIL "NUMA bind failed: expected node=$node actual=$numa_list" + fail=1 + fi + done + + [ $fail -eq 0 ] && tst_res $TPASS "NUMA aware optimization verified ($numa_num nodes)" +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/Makefile b/os_smoke_src/testcases/kernel/net/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/kernel/net/bond_tc001.sh b/os_smoke_src/testcases/kernel/net/bond_tc001.sh new file mode 100755 index 0000000..87d2cb3 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/bond_tc001.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: bond_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 网卡绑定功能bond验证 +# 前置条件: 内核支持bonding +# 步骤: +# 1. 确认内核已开启bonding配置并创建bond设备 +# 2. 将veth从设备加入bond,启用并验证 +# 3. 停用并删除bond及veth设备 +# 预期结果: +# 1. bonding模块加载成功,bond设备创建成功 +# 2. 从设备绑定成功,bond为UP状态 +# 3. 解绑和删除成功 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "bond设备创建与从设备绑定" + "bond设备启用与状态验证" + "bond设备解绑与删除" +) +tcnt=${#TCASE_DESC[@]} + +NET_DEV1="tveth0" +NET_DEV2="tveth1" +BOND_NAME="bond9" + +setup() +{ + lsmod | grep -q "^bonding" || modprobe bonding + if ! lsmod | grep -q "^bonding"; then + tst_brk $TCONF "bonding module not supported" + fi +} + +cleanup() +{ + ip link set ${NET_DEV1} nomaster 2>/dev/null || true + ip link set ${NET_DEV2} nomaster 2>/dev/null || true + ip link set ${BOND_NAME} down 2>/dev/null || true + ip link delete ${BOND_NAME} 2>/dev/null || true + ip link delete ${NET_DEV1} 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + case $n in + 0) + ip link add ${NET_DEV1} type veth peer name ${NET_DEV2} + if [ $? -ne 0 ]; then + tst_res $TFAIL "create veth pair failed" + return + fi + ip link add ${BOND_NAME} type bond + if [ $? -ne 0 ]; then + tst_res $TFAIL "create bond device failed" + return + fi + ip link set ${NET_DEV1} master ${BOND_NAME} || { tst_res $TFAIL "bind $NET_DEV1 to $BOND_NAME failed"; return; } + ip link set ${NET_DEV2} master ${BOND_NAME} || { tst_res $TFAIL "bind $NET_DEV2 to $BOND_NAME failed"; return; } + tst_res $TPASS "bond device created and slaves bound successfully" + ;; + 1) + ip link set ${BOND_NAME} up || { tst_res $TFAIL "up $BOND_NAME failed"; return; } + ip link set ${NET_DEV1} up || { tst_res $TFAIL "up $NET_DEV1 failed"; return; } + ip link set ${NET_DEV2} up || { tst_res $TFAIL "up $NET_DEV2 failed"; return; } + + ip -d link show ${BOND_NAME} | grep -qi "BOND" || { tst_res $TFAIL "bond device not detected"; return; } + ip a | grep ${BOND_NAME} | grep -q "UP" || { tst_res $TFAIL "bond not UP"; return; } + cat /proc/net/bonding/${BOND_NAME} | grep -q ${NET_DEV1} || { tst_res $TFAIL "$NET_DEV1 not in bond"; return; } + cat /proc/net/bonding/${BOND_NAME} | grep -q ${NET_DEV2} || { tst_res $TFAIL "$NET_DEV2 not in bond"; return; } + tst_res $TPASS "bond device UP and slaves verified" + ;; + 2) + ip link set ${NET_DEV1} nomaster || { tst_res $TFAIL "unbind $NET_DEV1 failed"; return; } + ip link set ${NET_DEV2} nomaster || { tst_res $TFAIL "unbind $NET_DEV2 failed"; return; } + ip link set ${BOND_NAME} down || { tst_res $TFAIL "down $BOND_NAME failed"; return; } + ip link delete ${BOND_NAME} || { tst_res $TFAIL "delete $BOND_NAME failed"; return; } + ip link delete ${NET_DEV1} || { tst_res $TFAIL "delete $NET_DEV1 failed"; return; } + tst_res $TPASS "bond device unbound and deleted successfully" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/ethtool_tc001.sh b/os_smoke_src/testcases/kernel/net/ethtool_tc001.sh new file mode 100755 index 0000000..e30e64b --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/ethtool_tc001.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ethtool_tc001 +# 用例类型: 功能用例 +# 优先级: P0 +# 用例描述: 验证ethtool在网卡上查询基础属性、驱动信息、队列和offload能力的功能 +# 前置条件: 无特殊前置条件 +# 步骤: +# 1. 执行ethtool查看网卡基本属性 +# 2. 执行ethtool -i查看驱动信息 +# 3. 执行ethtool -l查看队列配置 +# 4. 执行ethtool -k查看offload能力 +# 预期结果: +# 1. ethtool命令执行成功 +# 2. ethtool -i命令执行成功 +# 3. ethtool -l命令执行成功 +# 4. ethtool -k命令执行成功 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "ethtool基本属性查询" + "ethtool驱动信息查询" + "ethtool队列配置查询" + "ethtool offload能力查询" +) +tcnt=${#TCASE_DESC[@]} + +NET_DEV="" + +setup() +{ + ensure_command "ethtool" + NET_DEV=$(ip a | grep "UP group" | head -n 1 | awk '{print $2}' | awk -F ':' '{print $1}') + if [ -z "$NET_DEV" ]; then + tst_brk $TCONF "未找到UP状态的网络设备" + fi + tst_res $TINFO "使用网卡: $NET_DEV" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + local n=$1 + + case $n in + 0) + ethtool ${NET_DEV} > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "ethtool ${NET_DEV} failed" + else + tst_res $TPASS "ethtool ${NET_DEV} executed successfully" + fi + ;; + 1) + ethtool -i ${NET_DEV} > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "ethtool -i ${NET_DEV} failed" + else + tst_res $TPASS "ethtool -i ${NET_DEV} executed successfully" + fi + ;; + 2) + ethtool -l ${NET_DEV} > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "ethtool -l ${NET_DEV} failed" + else + tst_res $TPASS "ethtool -l ${NET_DEV} executed successfully" + fi + ;; + 3) + ethtool -k ${NET_DEV} > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "ethtool -k ${NET_DEV} failed" + else + tst_res $TPASS "ethtool -k ${NET_DEV} executed successfully" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/ip_link_tc001.sh b/os_smoke_src/testcases/kernel/net/ip_link_tc001.sh new file mode 100755 index 0000000..bc3627b --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/ip_link_tc001.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ip_link_tc001 +# 用例类型: 功能用例 +# 优先级: P1 +# 用例描述: 验证ip link在物理网卡上创建VLAN设备、设置up/down状态以及删除的基本功能 +# 前置条件: 无特殊前置条件 +# 步骤: +# 1. 获取默认路由网卡,创建VLAN设备并设置up +# 2. 设置VLAN设备down并删除 +# 预期结果: +# 1. VLAN设备创建和up成功 +# 2. VLAN设备down和删除成功 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "VLAN设备创建与启用" + "VLAN设备停用与删除" +) +tcnt=${#TCASE_DESC[@]} + +NET_DEV="" + +setup() +{ + NET_DEV=$(route 2>/dev/null | grep default | head -n1 | awk '{print $NF}') + if [ -z "$NET_DEV" ]; then + NET_DEV=$(ip route | grep default | head -n1 | awk '{print $5}') + fi + if [ -z "$NET_DEV" ]; then + tst_brk $TCONF "未找到默认路由网卡" + fi + tst_res $TINFO "使用网卡: $NET_DEV" +} + +cleanup() +{ + ip link del ${NET_DEV}.1 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + case $n in + 0) + ip link show $NET_DEV || { tst_res $TFAIL "Device ${NET_DEV} does not exist"; return; } + ip link add link $NET_DEV name ${NET_DEV}.1 type vlan id 3 || { tst_res $TFAIL "add ${NET_DEV}.1 failed"; return; } + ip link show ${NET_DEV}.1 || { tst_res $TFAIL "show ${NET_DEV}.1 failed"; return; } + ip link set ${NET_DEV}.1 up || { tst_res $TFAIL "up ${NET_DEV}.1 failed"; return; } + if ip link show ${NET_DEV}.1 | grep -q "UP"; then + tst_res $TPASS "VLAN device ${NET_DEV}.1 created and up" + else + tst_res $TFAIL "VLAN device ${NET_DEV}.1 not UP" + fi + ;; + 1) + ip link set ${NET_DEV}.1 down || { tst_res $TFAIL "down ${NET_DEV}.1 failed"; return; } + sleep 1 + ip link del ${NET_DEV}.1 || { tst_res $TFAIL "delete ${NET_DEV}.1 failed"; return; } + sleep 1 + if ip link show ${NET_DEV}.1 2>/dev/null; then + tst_res $TFAIL "Device ${NET_DEV}.1 still exists after deletion" + else + tst_res $TPASS "VLAN device ${NET_DEV}.1 deleted successfully" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/ip_route_tc001.sh b/os_smoke_src/testcases/kernel/net/ip_route_tc001.sh new file mode 100755 index 0000000..8e3e09b --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/ip_route_tc001.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ip_route_tc001 +# 用例类型: 功能用例 +# 优先级: P1 +# 用例描述: 验证ip route查看系统路由表和本地路由表的基本功能 +# 前置条件: 无特殊前置条件 +# 步骤: +# 1. 执行ip route list查看路由信息 +# 2. 执行ip route show table local查看本地路由表 +# 预期结果: +# 1. ip route list命令执行成功 +# 2. ip route show table local命令执行成功 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "ip route list查看路由" + "ip route show table local查看本地路由" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + return 0 +} + +cleanup() +{ + return 0 +} + +run_test() +{ + local n=$1 + + case $n in + 0) + ip route list > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "ip route list failed" + else + tst_res $TPASS "ip route list executed successfully" + fi + ;; + 1) + ip route show table local > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "ip route show table local failed" + else + tst_res $TPASS "ip route show table local executed successfully" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/ipv4_tc001.sh b/os_smoke_src/testcases/kernel/net/ipv4_tc001.sh new file mode 100755 index 0000000..d856b81 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/ipv4_tc001.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ipv4_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: IPV4协议验证 +# 前置条件: 无 +# 步骤: +# 1. 为网卡添加ipv4地址并验证 +# 2. 删除ipv4地址并验证删除成功 +# 预期结果: +# 1. 成功添加ipv4地址 +# 2. 成功删除ipv4地址 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "IPv4地址添加验证" + "IPv4地址删除验证" +) +tcnt=${#TCASE_DESC[@]} + +NET_DEV="" + +setup() +{ + NET_DEV=$(ip a | grep "UP group" | head -n 1 | awk '{print $2}' | awk -F ':' '{print $1}') + if [ -z "$NET_DEV" ]; then + tst_brk $TCONF "未找到UP状态的网络设备" + fi +} + +cleanup() +{ + # 确保测试IP被清理 + ip address del 192.168.128.10/24 dev ${NET_DEV} label ${NET_DEV}:1 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + case $n in + 0) + ip address add 192.168.128.10/24 dev ${NET_DEV} label ${NET_DEV}:1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "add ipv4 address failed" + return + fi + if ip address show dev ${NET_DEV} | grep inet | grep -q "192.168.128.10/24"; then + tst_res $TPASS "ipv4 address added and verified on $NET_DEV" + else + tst_res $TFAIL "ipv4 address not found after adding" + fi + ;; + 1) + ip address del 192.168.128.10/24 dev ${NET_DEV} label ${NET_DEV}:1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "delete ipv4 address failed" + return + fi + if ip address show dev ${NET_DEV} | grep inet | grep -q "192.168.128.10/24"; then + tst_res $TFAIL "ipv4 address still exists after deletion" + else + tst_res $TPASS "ipv4 address deleted successfully on $NET_DEV" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/ipv6_tc001.sh b/os_smoke_src/testcases/kernel/net/ipv6_tc001.sh new file mode 100755 index 0000000..d2c6c82 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/ipv6_tc001.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: ipv6_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: IPV6协议验证 +# 前置条件: 无 +# 步骤: +# 1. 为网卡添加ipv6地址并验证 +# 2. 删除ipv6地址并验证删除成功 +# 预期结果: +# 1. 成功添加ipv6地址 +# 2. 成功删除ipv6地址 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "IPv6地址添加验证" + "IPv6地址删除验证" +) +tcnt=${#TCASE_DESC[@]} + +NET_DEV="" + +setup() +{ + NET_DEV=$(ip a | grep "UP group" | head -n 1 | awk '{print $2}' | awk -F ':' '{print $1}') + if [ -z "$NET_DEV" ]; then + tst_brk $TCONF "未找到UP状态的网络设备" + fi +} + +cleanup() +{ + ip -6 address del 1001::1/64 dev ${NET_DEV} 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + case $n in + 0) + ip -6 address add 1001::1/64 dev ${NET_DEV} + if [ $? -ne 0 ]; then + tst_res $TFAIL "add ipv6 address failed" + return + fi + if ip -6 address show dev ${NET_DEV} | grep inet6 | grep -q "1001::1/64"; then + tst_res $TPASS "ipv6 address added and verified on $NET_DEV" + else + tst_res $TFAIL "ipv6 address not found after adding" + fi + ;; + 1) + ip -6 address del 1001::1/64 dev ${NET_DEV} + if [ $? -ne 0 ]; then + tst_res $TFAIL "delete ipv6 address failed" + return + fi + if ip -6 address show dev ${NET_DEV} | grep inet6 | grep -q "1001::1/64"; then + tst_res $TFAIL "ipv6 address still exists after deletion" + else + tst_res $TPASS "ipv6 address deleted successfully on $NET_DEV" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/net_link_tc001.sh b/os_smoke_src/testcases/kernel/net/net_link_tc001.sh new file mode 100755 index 0000000..616723f --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/net_link_tc001.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: net_link_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 网络链路故障检测,链路状态查询 +# 前置条件: 系统运行正常,已正确安装网卡,且已安装tcpdump包 +# 步骤: +# 1. 使用ethtool查询网卡是否正常物理连接 +# 2. 使用ping测试网络是否联通 +# 3. 执行tcpdump命令监听网卡的链路状态 +# 预期结果: +# 1. Link detected: yes +# 2. 可以使用ping命令 +# 3. 可以捕获对应网口的数据包 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "ethtool链路检测" + "ping连通性测试" + "tcpdump抓包验证" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + install_packages "tcpdump ethtool" + NET_DEV=$(ip a | grep "UP group" | head -n 1 | awk '{print $2}' | awk -F ':' '{print $1}') + if [ -z "$NET_DEV" ]; then + tst_brk $TCONF "未找到UP状态的网络设备" + fi + tst_res $TINFO "使用网卡: $NET_DEV" +} + +cleanup() +{ + return 0 +} + +run_test() +{ + local n=$1 + + case $n in + 0) + ethtool ${NET_DEV} | grep "Link detected: yes" + if [ $? -ne 0 ]; then + tst_res $TFAIL "link query failed on $NET_DEV" + else + tst_res $TPASS "ethtool link detected on $NET_DEV" + fi + ;; + 1) + ping 127.0.0.1 -c 3 + if [ $? -ne 0 ]; then + tst_res $TFAIL "ping 127.0.0.1 failed" + else + tst_res $TPASS "ping 127.0.0.1 success" + fi + ;; + 2) + tcpdump -i ${NET_DEV} -c 3 + if [ $? -ne 0 ]; then + tst_res $TFAIL "tcpdump on $NET_DEV failed" + else + tst_res $TPASS "tcpdump captured packets on $NET_DEV" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/nettools_tc001.sh b/os_smoke_src/testcases/kernel/net/nettools_tc001.sh new file mode 100755 index 0000000..8744fac --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/nettools_tc001.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: nettools_tc001 +# 用例类型: 功能用例 +# 优先级: P0 +# 用例描述: 验证传统网络工具arp/ifconfig/route的基本信息查看功能 +# 前置条件: 无特殊前置条件 +# 步骤: +# 1. 执行arp -a查看ARP缓存 +# 2. 执行ifconfig查看网卡信息 +# 3. 执行route -n查看路由表 +# 预期结果: +# 1. arp -a命令执行成功 +# 2. ifconfig命令执行成功 +# 3. route -n命令执行成功 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "arp -a查看ARP缓存" + "ifconfig查看网卡信息" + "route -n查看路由表" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + return 0 +} + +cleanup() +{ + return 0 +} + +run_test() +{ + local n=$1 + + case $n in + 0) + arp -a > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "arp -a failed" + else + tst_res $TPASS "arp -a executed successfully" + fi + ;; + 1) + ifconfig > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "ifconfig failed" + else + tst_res $TPASS "ifconfig executed successfully" + fi + ;; + 2) + route -n > /dev/null 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "route -n failed" + else + tst_res $TPASS "route -n executed successfully" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/network_libs.sh b/os_smoke_src/testcases/kernel/net/network_libs.sh new file mode 100644 index 0000000..58f9d1f --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/network_libs.sh @@ -0,0 +1,573 @@ +#!/bin/bash +#**************************************************# +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +# 网络测试用例通用函数库 +# 文件名: network_libs.sh +# 描述: 提供TCP/UDP/HTTP等网络测试用例的通用函数 +#**************************************************# + +# 默认配置参数 +DEFAULT_TEST_PORT_TCP=8888 +DEFAULT_TEST_PORT_UDP=9999 +DEFAULT_TEST_PORT_HTTP=8080 +DEFAULT_TEST_PORT_HTTPS=8443 +NETWORK_TEST_DIR="/tmp/network_test" +NETWORK_LOG_DIR="/var/log/network_test" + +#**************************************************# +# nc监听兼容函数:自动兼容GNU nc和OpenBSD nc +# 用法: nc_listen [-u] PORT +#**************************************************# +nc_listen() { + local args=() + local port="" + for arg in "$@"; do + if [[ "$arg" =~ ^[0-9]+$ ]] && [ -z "$port" ]; then + port="$arg" + else + args+=("$arg") + fi + done + nc "${args[@]}" -l -p "$port" 2>/dev/null || nc "${args[@]}" -l "$port" +} + +#**************************************************# +# 验证网络测试环境 +#**************************************************# +verify_network_environment() { + # 检查必要的网络工具 + local required_tools="netstat ss nc telnet curl wget" + local missing_tools="" + + for tool in $required_tools; do + if ! command -v "$tool" >/dev/null 2>&1; then + missing_tools="$missing_tools $tool" + fi + done + + if [ -n "$missing_tools" ]; then + tst_res $TINFO "Some network tools are missing:$missing_tools" + tst_res $TINFO "Tests will use available alternatives" + fi + + # 创建测试目录 + mkdir -p "$NETWORK_TEST_DIR" "$NETWORK_LOG_DIR" + + tst_res $TINFO "Network environment verified successfully" + return 0 +} + +#**************************************************# +# 检查端口是否可用 +# 参数: $1 - 端口, $2 - 协议(tcp/udp) +#**************************************************# +is_port_available() { + local port="$1" + local protocol="${2:-tcp}" + + if [ "$protocol" = "tcp" ]; then + if netstat -tlnp 2>/dev/null | grep -q ":$port "; then + return 1 # 端口被占用 + elif ss -tlnp 2>/dev/null | grep -q ":$port "; then + return 1 # 端口被占用 + fi + elif [ "$protocol" = "udp" ]; then + if netstat -ulnp 2>/dev/null | grep -q ":$port "; then + return 1 # 端口被占用 + elif ss -ulnp 2>/dev/null | grep -q ":$port "; then + return 1 # 端口被占用 + fi + fi + + return 0 # 端口可用 +} + +#**************************************************# +# 查找可用端口 +# 参数: $1 - 起始端口, $2 - 协议(tcp/udp) +#**************************************************# +find_available_port() { + local start_port="${1:-8000}" + local protocol="${2:-tcp}" + local port="$start_port" + + while [ $port -lt 65535 ]; do + if is_port_available "$port" "$protocol"; then + echo "$port" + return 0 + fi + port=$((port + 1)) + done + + tst_res $TFAIL "No available port found starting from $start_port" + return 1 +} + +#**************************************************# +# 启动TCP服务器 +# 参数: $1 - 端口, $2 - 服务器类型(simple/echo/http) +#**************************************************# +start_tcp_server() { + local port="$1" + local server_type="${2:-simple}" + local pid_file="$NETWORK_TEST_DIR/tcp_server_${port}.pid" + + if ! is_port_available "$port" "tcp"; then + tst_res $TFAIL "Port $port is already in use" + return 1 + fi + + case "$server_type" in + "simple") + # 简单的TCP服务器 + if command -v nc >/dev/null 2>&1; then + nc_listen "$port" > "$NETWORK_TEST_DIR/tcp_server_${port}.log" 2>&1 & + echo $! > "$pid_file" + else + tst_res $TFAIL "netcat (nc) is not available for TCP server" + return 1 + fi + ;; + "echo") + # Echo TCP服务器 + if command -v nc >/dev/null 2>&1; then + while true; do nc_listen "$port" -c 'while read line; do echo "ECHO: $line"; done'; done > "$NETWORK_TEST_DIR/tcp_echo_${port}.log" 2>&1 & + echo $! > "$pid_file" + else + # 使用socat作为备选 + if command -v socat >/dev/null 2>&1; then + socat TCP-LISTEN:"$port",fork EXEC:'/bin/cat' > "$NETWORK_TEST_DIR/tcp_echo_${port}.log" 2>&1 & + echo $! > "$pid_file" + else + tst_res $TFAIL "Neither nc nor socat is available for echo server" + return 1 + fi + fi + ;; + "http") + # 简单的HTTP服务器 + if command -v python3 >/dev/null 2>&1; then + cd "$NETWORK_TEST_DIR" + echo "

Test HTTP Server

Port: $port

" > index.html + python3 -m http.server "$port" > "$NETWORK_TEST_DIR/http_server_${port}.log" 2>&1 & + echo $! > "$pid_file" + elif command -v python >/dev/null 2>&1; then + cd "$NETWORK_TEST_DIR" + echo "

Test HTTP Server

Port: $port

" > index.html + python -m SimpleHTTPServer "$port" > "$NETWORK_TEST_DIR/http_server_${port}.log" 2>&1 & + echo $! > "$pid_file" + else + tst_res $TFAIL "Python is not available for HTTP server" + return 1 + fi + ;; + esac + + # 等待服务器启动 + sleep 2 + + # 验证服务器是否启动成功 + if [ -f "$pid_file" ] && kill -0 "$(cat $pid_file)" 2>/dev/null; then + tst_res $TINFO "TCP server ($server_type) started on port $port" + return 0 + else + tst_res $TFAIL "Failed to start TCP server on port $port" + return 1 + fi +} + +#**************************************************# +# 停止TCP服务器 +# 参数: $1 - 端口 +#**************************************************# +stop_tcp_server() { + local port="$1" + local pid_file="$NETWORK_TEST_DIR/tcp_server_${port}.pid" + + if [ -f "$pid_file" ]; then + local pid + pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + kill "$pid" 2>/dev/null || true + sleep 1 + # 强制杀死如果还在运行 + kill -9 "$pid" 2>/dev/null || true + fi + rm -f "$pid_file" + fi + + # 杀死可能的相关进程 + pkill -f ":$port" 2>/dev/null || true + + tst_res $TINFO "TCP server on port $port stopped" +} + +#**************************************************# +# 启动UDP服务器 +# 参数: $1 - 端口, $2 - 服务器类型(simple/echo) +#**************************************************# +start_udp_server() { + local port="$1" + local server_type="${2:-simple}" + local pid_file="$NETWORK_TEST_DIR/udp_server_${port}.pid" + + if ! is_port_available "$port" "udp"; then + tst_res $TFAIL "UDP port $port is already in use" + return 1 + fi + + case "$server_type" in + "simple") + # 简单的UDP服务器 + if command -v nc >/dev/null 2>&1; then + nc_listen -u "$port" > "$NETWORK_TEST_DIR/udp_server_${port}.log" 2>&1 & + echo $! > "$pid_file" + else + tst_res $TFAIL "netcat (nc) is not available for UDP server" + return 1 + fi + ;; + "echo") + # Echo UDP服务器 + if command -v socat >/dev/null 2>&1; then + socat UDP-LISTEN:"$port",fork EXEC:'/bin/cat' > "$NETWORK_TEST_DIR/udp_echo_${port}.log" 2>&1 & + echo $! > "$pid_file" + elif command -v nc >/dev/null 2>&1; then + # 使用nc的UDP echo(可能不完全支持) + while true; do nc_listen -u "$port" -c 'while read line; do echo "UDP_ECHO: $line"; done'; done > "$NETWORK_TEST_DIR/udp_echo_${port}.log" 2>&1 & + echo $! > "$pid_file" + else + tst_res $TFAIL "Neither socat nor nc is available for UDP echo server" + return 1 + fi + ;; + esac + + # 等待服务器启动 + sleep 2 + + # 验证服务器是否启动成功 + if [ -f "$pid_file" ] && kill -0 "$(cat $pid_file)" 2>/dev/null; then + tst_res $TINFO "UDP server ($server_type) started on port $port" + return 0 + else + tst_res $TFAIL "Failed to start UDP server on port $port" + return 1 + fi +} + +#**************************************************# +# 停止UDP服务器 +# 参数: $1 - 端口 +#**************************************************# +stop_udp_server() { + local port="$1" + local pid_file="$NETWORK_TEST_DIR/udp_server_${port}.pid" + + if [ -f "$pid_file" ]; then + local pid + pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + kill "$pid" 2>/dev/null || true + sleep 1 + kill -9 "$pid" 2>/dev/null || true + fi + rm -f "$pid_file" + fi + + # 杀死可能的相关进程 + pkill -f "UDP.*:$port" 2>/dev/null || true + + tst_res $TINFO "UDP server on port $port stopped" +} + +#**************************************************# +# 测试TCP连接 +# 参数: $1 - 主机, $2 - 端口, $3 - 超时时间(秒) +#**************************************************# +test_tcp_connection() { + local host="$1" + local port="$2" + local timeout="${3:-5}" + + # 尝试多种方法测试TCP连接 + if command -v nc >/dev/null 2>&1; then + if echo "test" | nc -w "$timeout" "$host" "$port" >/dev/null 2>&1; then + tst_res $TINFO "TCP connection to $host:$port successful" + return 0 + fi + elif command -v telnet >/dev/null 2>&1; then + if echo "quit" | telnet "$host" "$port" 2>/dev/null | grep -q "Connected"; then + tst_res $TINFO "TCP connection to $host:$port successful" + return 0 + fi + elif command -v bash >/dev/null 2>&1; then + # 使用bash内置的TCP连接 + if exec 3<>"/dev/tcp/$host/$port" 2>/dev/null; then + exec 3>&- + tst_res $TINFO "TCP connection to $host:$port successful" + return 0 + fi + fi + + tst_res $TFAIL "TCP connection to $host:$port failed" + return 1 +} + +#**************************************************# +# 测试UDP连接 +# 参数: $1 - 主机, $2 - 端口, $3 - 测试消息 +#**************************************************# +test_udp_connection() { + local host="$1" + local port="$2" + local message="${3:-test_udp}" + + if command -v nc >/dev/null 2>&1; then + local response + response=$(echo "$message" | nc -u -w 2 "$host" "$port" 2>/dev/null) + if [ $? -eq 0 ]; then + tst_res $TINFO "UDP connection to $host:$port successful" + if [ -n "$response" ]; then + tst_res $TINFO "UDP response: $response" + fi + return 0 + fi + fi + + tst_res $TFAIL "UDP connection to $host:$port failed" + return 1 +} + +#**************************************************# +# 测试HTTP连接 +# 参数: $1 - URL, $2 - 期望状态码, $3 - 超时时间 +#**************************************************# +test_http_connection() { + local url="$1" + local expected_code="${2:-200}" + local timeout="${3:-10}" + + if command -v curl >/dev/null 2>&1; then + local status_code + status_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout "$timeout" "$url" 2>/dev/null) + if [ "$status_code" = "$expected_code" ]; then + tst_res $TINFO "HTTP connection to $url successful (Status: $status_code)" + return 0 + else + tst_res $TFAIL "HTTP connection to $url failed (Expected: $expected_code, Got: $status_code)" + return 1 + fi + elif command -v wget >/dev/null 2>&1; then + if wget -q --timeout="$timeout" --spider "$url" 2>/dev/null; then + tst_res $TINFO "HTTP connection to $url successful" + return 0 + else + tst_res $TFAIL "HTTP connection to $url failed" + return 1 + fi + else + tst_res $TFAIL "Neither curl nor wget is available for HTTP testing" + return 1 + fi +} + +#**************************************************# +# 网络延迟测试 +# 参数: $1 - 目标主机, $2 - 测试次数 +#**************************************************# +test_network_latency() { + local host="$1" + local count="${2:-5}" + + if command -v ping >/dev/null 2>&1; then + local result + result=$(ping -c "$count" "$host" 2>/dev/null | grep "avg" | awk -F'/' '{print $5}') + if [ -n "$result" ]; then + tst_res $TINFO "Network latency to $host: ${result}ms average" + return 0 + else + tst_res $TFAIL "Network latency test to $host failed" + return 1 + fi + else + tst_res $TFAIL "ping command is not available for latency testing" + return 1 + fi +} + +#**************************************************# +# 网络带宽测试 +# 参数: $1 - 目标主机, $2 - 端口, $3 - 测试时间(秒) +#**************************************************# +test_network_bandwidth() { + local host="$1" + local port="$2" + local duration="${3:-10}" + + if command -v iperf3 >/dev/null 2>&1; then + local result + result=$(iperf3 -c "$host" -p "$port" -t "$duration" 2>/dev/null | grep "sender" | awk '{print $7" "$8}') + if [ -n "$result" ]; then + tst_res $TINFO "Network bandwidth to $host:$port: $result" + return 0 + else + tst_res $TFAIL "Network bandwidth test to $host:$port failed" + return 1 + fi + elif command -v iperf >/dev/null 2>&1; then + local result + result=$(iperf -c "$host" -p "$port" -t "$duration" 2>/dev/null | grep "Bandwidth" | awk '{print $7" "$8}') + if [ -n "$result" ]; then + tst_res $TINFO "Network bandwidth to $host:$port: $result" + return 0 + else + tst_res $TFAIL "Network bandwidth test to $host:$port failed" + return 1 + fi + else + tst_res $TINFO "iperf/iperf3 not available, skipping bandwidth test" + return 0 + fi +} + +#**************************************************# +# 端口扫描测试 +# 参数: $1 - 目标主机, $2 - 端口范围(如 80-90) +#**************************************************# +scan_ports() { + local host="$1" + local port_range="$2" + local start_port end_port + + if echo "$port_range" | grep -q "-"; then + start_port=$(echo "$port_range" | cut -d'-' -f1) + end_port=$(echo "$port_range" | cut -d'-' -f2) + else + start_port="$port_range" + end_port="$port_range" + fi + + tst_res $TINFO "Scanning ports $start_port-$end_port on $host..." + + local open_ports="" + local port=$start_port + + while [ $port -le $end_port ]; do + if command -v nc >/dev/null 2>&1; then + if nc -z -w 1 "$host" "$port" 2>/dev/null; then + open_ports="$open_ports $port" + fi + elif command -v bash >/dev/null 2>&1; then + if exec 3<>"/dev/tcp/$host/$port" 2>/dev/null; then + exec 3>&- + open_ports="$open_ports $port" + fi + fi + port=$((port + 1)) + done + + if [ -n "$open_ports" ]; then + tst_res $TINFO "Open ports on $host:$open_ports" + else + tst_res $TINFO "No open ports found on $host in range $port_range" + fi + + return 0 +} + +#**************************************************# +# 创建网络配置文件 +# 参数: $1 - 配置类型, $2 - 配置内容 +#**************************************************# +create_network_config() { + local config_type="$1" + local config_content="$2" + local config_file="$NETWORK_TEST_DIR/${config_type}_config.txt" + + echo "$config_content" > "$config_file" + tst_res $TINFO "Network config created: $config_file" + return 0 +} + +#**************************************************# +# 监控网络连接 +# 参数: $1 - 监控时间(秒), $2 - 输出文件 +#**************************************************# +monitor_network_connections() { + local duration="${1:-30}" + local output_file="${2:-$NETWORK_TEST_DIR/network_monitor.log}" + + tst_res $TINFO "Monitoring network connections for ${duration} seconds..." + + local end_time=$(($(date +%s) + duration)) + + while [ $(date +%s) -lt $end_time ]; do + echo "=== $(date) ===" >> "$output_file" + netstat -an >> "$output_file" 2>/dev/null || ss -an >> "$output_file" 2>/dev/null + echo "" >> "$output_file" + sleep 5 + done + + tst_res $TINFO "Network monitoring completed, results in: $output_file" + return 0 +} + +#**************************************************# +# 清理网络测试环境 +#**************************************************# +cleanup_network_test() { + tst_res $TINFO "Cleaning up network test environment..." + + # 停止所有测试服务器 + for pid_file in "$NETWORK_TEST_DIR"/*.pid; do + if [ -f "$pid_file" ]; then + local pid + pid=$(cat "$pid_file") + kill "$pid" 2>/dev/null || true + kill -9 "$pid" 2>/dev/null || true + rm -f "$pid_file" + fi + done + + # 清理测试文件 + rm -rf "$NETWORK_TEST_DIR" 2>/dev/null || true + rm -rf "$NETWORK_LOG_DIR" 2>/dev/null || true + + tst_res $TINFO "Network test environment cleaned up" +} + +#**************************************************# +# 显示网络状态信息 +#**************************************************# +show_network_status() { + tst_res $TINFO "=== Network Status Information ===" + + # 网络接口信息 + echo "✓ Network Interfaces:" + ip addr show 2>/dev/null | grep "inet " | awk '{print " - "$2}' || \ + ifconfig 2>/dev/null | grep "inet " | awk '{print " - "$2}' + + # 路由信息 + echo "✓ Default Route:" + ip route show default 2>/dev/null | head -1 || \ + route -n 2>/dev/null | grep "^0.0.0.0" | head -1 + + # 监听端口 + echo "✓ Listening Ports:" + netstat -tlnp 2>/dev/null | grep "LISTEN" | awk '{print " TCP - "$4}' | head -10 || \ + ss -tlnp 2>/dev/null | grep "LISTEN" | awk '{print " TCP - "$4}' | head -10 + + netstat -ulnp 2>/dev/null | awk '{print " UDP - "$4}' | head -5 || \ + ss -ulnp 2>/dev/null | awk '{print " UDP - "$4}' | head -5 + + # DNS配置 + echo "✓ DNS Servers:" + if [ -f /etc/resolv.conf ]; then + grep "nameserver" /etc/resolv.conf | awk '{print " - "$2}' + fi +} \ No newline at end of file diff --git a/os_smoke_src/testcases/kernel/net/tcp_concurrent_client.py b/os_smoke_src/testcases/kernel/net/tcp_concurrent_client.py new file mode 100644 index 0000000..1a7d856 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/tcp_concurrent_client.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# ############################################# +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 +# ############################################# +import socket +import threading +import time +import sys + + +def tcp_client_worker(server_port: int, client_id: int, num_requests: int) -> None: + results = {"success": 0, "failed": 0, "total_time": 0.0} + + for i in range(num_requests): + start_time = time.time() + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(5) + sock.connect(("127.0.0.1", server_port)) + + message = f"Client {client_id} Request {i + 1}\n" + sock.send(message.encode()) + + response = sock.recv(1024) + if response: + results["success"] += 1 + + sock.close() + except Exception as exc: + results["failed"] += 1 + print(f"Client {client_id} Request {i + 1} failed: {exc}") + + results["total_time"] += time.time() - start_time + time.sleep(0.1) + + avg_time = results["total_time"] / num_requests if num_requests > 0 else 0.0 + print( + f"Client {client_id}: {results['success']} success, " + f"{results['failed']} failed, avg time: {avg_time:.3f}s" + ) + + +def run_concurrent_test(server_port: int, num_clients: int, requests_per_client: int) -> None: + threads = [] + start_time = time.time() + + for i in range(num_clients): + thread = threading.Thread( + target=tcp_client_worker, + args=(server_port, i + 1, requests_per_client), + ) + threads.append(thread) + thread.start() + + for thread in threads: + thread.join() + + total_time = time.time() - start_time + total_requests = num_clients * requests_per_client + print( + f"Concurrent test completed: {total_requests} requests by {num_clients} " + f"clients in {total_time:.2f}s" + ) + print( + f"Average throughput: {total_requests / total_time:.2f} requests/second" + ) + + +if __name__ == "__main__": + if len(sys.argv) < 4: + print( + "Usage: python3 tcp_concurrent_client.py " + " " + ) + sys.exit(1) + + port_arg = int(sys.argv[1]) + clients_arg = int(sys.argv[2]) + requests_arg = int(sys.argv[3]) + + run_concurrent_test(port_arg, clients_arg, requests_arg) diff --git a/os_smoke_src/testcases/kernel/net/tcp_concurrent_tc003.sh b/os_smoke_src/testcases/kernel/net/tcp_concurrent_tc003.sh new file mode 100755 index 0000000..1e35635 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/tcp_concurrent_tc003.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: tcp_concurrent_tc003 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: TCP并发连接测试 - 多客户端同时连接与长连接稳定性 +# 前置条件: 安装网络测试工具包 +# 步骤: +# 1. 启动Echo服务器并运行并发客户端测试 +# 2. 使用socat进行长连接稳定性测试 +# 预期结果: +# 1. 并发客户端测试完成,日志包含完成信息 +# 2. 长连接连续发送多条消息均能收到回显 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ./network_libs.sh + +TCP_ECHO_PORT=7777 +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +ECHO_SERVER_PID="" +SOCAT_PID="" + +TCASE_DESC=( + "TCP并发连接测试" + "TCP长连接稳定性测试" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + pkill -9 -f "python3.*tcp_.*_server" 2>/dev/null || true + pkill -9 -f "socat" 2>/dev/null || true + sleep 1 + verify_network_environment || tst_brk $TCONF "网络环境验证失败" + mkdir -p /tmp/tcp_test/{logs,scripts} +} + +cleanup() +{ + [ -n "$ECHO_SERVER_PID" ] && kill $ECHO_SERVER_PID 2>/dev/null || true + [ -n "$SOCAT_PID" ] && kill $SOCAT_PID 2>/dev/null || true + pkill -f "python3.*tcp_.*_server" 2>/dev/null || true + pkill -f socat 2>/dev/null || true + cleanup_network_test + rm -rf /tmp/tcp_test /tmp/tcp_*.log +} + +run_test() +{ + local n=$1 + + case $n in + 0) + python3 "${SCRIPT_DIR}/tcp_echo_server.py" "$TCP_ECHO_PORT" > /tmp/tcp_echo_server.log 2>&1 & + ECHO_SERVER_PID=$! + sleep 2 + netstat -tlnp 2>/dev/null | grep -q ":$TCP_ECHO_PORT " || ss -tlnp 2>/dev/null | grep -q ":$TCP_ECHO_PORT " + if [ $? -ne 0 ]; then + tst_res $TFAIL "TCP Echo server not listening" + return + fi + python3 "${SCRIPT_DIR}/tcp_concurrent_client.py" "$TCP_ECHO_PORT" 5 10 > /tmp/tcp_concurrent_result.log 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "Concurrent connection test failed" + return + fi + if grep -q "Concurrent test completed" /tmp/tcp_concurrent_result.log; then + local throughput + throughput=$(grep "Average throughput" /tmp/tcp_concurrent_result.log | awk '{print $3, $4}') + tst_res $TPASS "Concurrent connection test passed, throughput: $throughput" + else + tst_res $TFAIL "Concurrent test verification failed" + fi + ;; + 1) + if ! command -v socat >/dev/null 2>&1; then + tst_brk $TCONF "socat not installed, skip long connection test" + fi + socat TCP-LISTEN:9999,reuseaddr,fork EXEC:'/bin/cat' & + SOCAT_PID=$! + sleep 2 + if ! kill -0 $SOCAT_PID 2>/dev/null; then + tst_res $TFAIL "Failed to start socat echo server" + return + fi + + local long_conn_fail=0 + exec 3<>/dev/tcp/127.0.0.1/9999 + if [ $? -ne 0 ]; then + tst_res $TFAIL "Failed to connect to socat echo server" + return + fi + for i in {1..10}; do + echo "Message $i" >&3 + local response="" + read -t 5 response <&3 + if ! echo "$response" | grep -q "Message $i"; then + tst_res $TFAIL "Long connection message $i failed, got: '$response'" + long_conn_fail=1 + break + fi + sleep 0.5 + done + exec 3>&- + + if [ $long_conn_fail -eq 0 ]; then + tst_res $TPASS "Long connection stability test passed" + fi + kill $SOCAT_PID 2>/dev/null || true + kill $ECHO_SERVER_PID 2>/dev/null || true + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/tcp_echo_server.py b/os_smoke_src/testcases/kernel/net/tcp_echo_server.py new file mode 100644 index 0000000..8cfa5f4 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/tcp_echo_server.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# ############################################# +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 +# ############################################# +import socket +import threading +import time +import sys + + +def handle_client(client_socket, addr): + print(f"Connection from {addr}") + try: + # 只处理一次请求,响应后立即关闭连接,避免客户端长时间阻塞 + data = client_socket.recv(1024) + if data: + response = f"ECHO [{time.strftime('%H:%M:%S')}]: {data.decode()}" + client_socket.send(response.encode()) + except Exception as exc: + print(f"Error handling client {addr}: {exc}") + finally: + client_socket.close() + + +def start_server(port: int) -> None: + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + try: + server.bind(("127.0.0.1", port)) + server.listen(50) + print(f"TCP Echo server listening on port {port}") + + while True: + client, addr = server.accept() + client_thread = threading.Thread(target=handle_client, args=(client, addr)) + client_thread.daemon = True + client_thread.start() + except KeyboardInterrupt: + print("Server shutdown") + finally: + server.close() + + +if __name__ == "__main__": + port_arg = int(sys.argv[1]) if len(sys.argv) > 1 else 7777 + start_server(port_arg) diff --git a/os_smoke_src/testcases/kernel/net/tcp_echo_tc002.sh b/os_smoke_src/testcases/kernel/net/tcp_echo_tc002.sh new file mode 100755 index 0000000..86c4f45 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/tcp_echo_tc002.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: tcp_echo_tc002 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: TCP套接字编程测试 - Echo服务器与HTTP over TCP通信 +# 前置条件: 安装网络测试工具包 +# 步骤: +# 1. 启动TCP Echo服务器并验证回显功能 +# 2. 启动TCP HTTP服务器并验证HTTP功能 +# 预期结果: +# 1. Echo服务器正确回显消息 +# 2. HTTP服务器返回正确内容 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +source ./network_libs.sh + +TCP_ECHO_PORT=7777 +TCP_HTTP_PORT=8888 +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +ECHO_SERVER_PID="" +HTTP_SERVER_PID="" + +TCASE_DESC=( + "TCP Echo服务器回显功能" + "HTTP over TCP通信功能" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + pkill -9 -f "python3.*tcp_.*_server" 2>/dev/null || true + pkill -9 -f "nc.*-l" 2>/dev/null || true + sleep 1 + ensure_command "python3" + ensure_command "nc" "nmap-ncat" + verify_network_environment || tst_brk $TCONF "网络环境验证失败" + mkdir -p /tmp/tcp_test/logs +} + +cleanup() +{ + [ -n "$ECHO_SERVER_PID" ] && kill $ECHO_SERVER_PID 2>/dev/null || true + [ -n "$HTTP_SERVER_PID" ] && kill $HTTP_SERVER_PID 2>/dev/null || true + pkill -f "python3.*tcp_.*_server" 2>/dev/null || true + pkill -f "nc.*-l" 2>/dev/null || true + cleanup_network_test + rm -rf /tmp/tcp_test /tmp/tcp_*.log +} + +run_test() +{ + local n=$1 + + case $n in + 0) + python3 "${SCRIPT_DIR}/tcp_echo_server.py" "$TCP_ECHO_PORT" > /tmp/tcp_echo_server.log 2>&1 & + ECHO_SERVER_PID=$! + sleep 2 + netstat -tlnp 2>/dev/null | grep -q ":$TCP_ECHO_PORT " || ss -tlnp 2>/dev/null | grep -q ":$TCP_ECHO_PORT " + if [ $? -ne 0 ]; then + tst_res $TFAIL "TCP Echo server not listening" + return + fi + echo "Hello TCP Server" | nc -w 3 127.0.0.1 $TCP_ECHO_PORT > /tmp/tcp_echo_response.log 2>&1 + if grep -q "ECHO.*Hello TCP Server" /tmp/tcp_echo_response.log; then + tst_res $TPASS "TCP Echo test passed" + else + tst_res $TFAIL "TCP Echo test failed" + fi + ;; + 1) + python3 "${SCRIPT_DIR}/tcp_http_server.py" "$TCP_HTTP_PORT" > /tmp/tcp_http_server.log 2>&1 & + HTTP_SERVER_PID=$! + for i in {1..10}; do + netstat -tlnp 2>/dev/null | grep -q ":$TCP_HTTP_PORT " && break + ss -tlnp 2>/dev/null | grep -q ":$TCP_HTTP_PORT " && break + sleep 0.5 + done + if curl -s http://127.0.0.1:$TCP_HTTP_PORT/ | grep -q "TCP HTTP Test Server"; then + tst_res $TPASS "HTTP over TCP test passed" + else + tst_res $TFAIL "HTTP over TCP test failed" + fi + kill $ECHO_SERVER_PID $HTTP_SERVER_PID 2>/dev/null || true + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/tcp_http_server.py b/os_smoke_src/testcases/kernel/net/tcp_http_server.py new file mode 100644 index 0000000..22ff76f --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/tcp_http_server.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# ############################################# +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 +# ############################################# +import http.server +import socketserver +import json +import sys +from datetime import datetime + + +class TCPTestHandler(http.server.SimpleHTTPRequestHandler): + def do_GET(self): + if self.path == "/": + self.send_response(200) + self.send_header("Content-type", "application/json") + self.end_headers() + response = { + "message": "TCP HTTP Test Server", + "timestamp": datetime.now().isoformat(), + "client": self.client_address[0], + "method": "GET", + } + self.wfile.write(json.dumps(response, indent=2).encode()) + else: + super().do_GET() + + +if __name__ == "__main__": + port_arg = int(sys.argv[1]) if len(sys.argv) > 1 else 8888 + with socketserver.TCPServer(("127.0.0.1", port_arg), TCPTestHandler) as httpd: + print(f"TCP HTTP server listening on port {port_arg}") + httpd.serve_forever() diff --git a/os_smoke_src/testcases/kernel/net/tcp_iperf_tc001.sh b/os_smoke_src/testcases/kernel/net/tcp_iperf_tc001.sh new file mode 100755 index 0000000..91c7d07 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/tcp_iperf_tc001.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: tcp_iperf_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: TCP基础连接测试 - iperf3性能测试 +# 前置条件: 系统支持网络测试 +# 步骤: +# 1. 启动iperf3服务器并检查端口监听 +# 2. 执行iperf3客户端性能测试并验证结果 +# 预期结果: +# 1. iperf3服务进程启动成功,端口正常监听 +# 2. 测试结果包含sender和receiver统计信息 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +source ./network_libs.sh + +TCP_IPERF_PORT=5201 +IPERF_PID="" + +TCASE_DESC=( + "iperf3服务器启动与端口监听" + "iperf3客户端性能测试与结果验证" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + pkill -9 -f "iperf3" 2>/dev/null || true + sleep 1 + + ensure_command "iperf3" + + verify_network_environment || tst_brk $TCONF "网络环境验证失败" + mkdir -p /tmp/tcp_test/logs +} + +cleanup() +{ + if pgrep -f "iperf3" > /dev/null; then + pkill -9 -f "iperf3" 2>/dev/null || true + for i in {1..5}; do + pgrep -f "iperf3" > /dev/null || break + sleep 1 + done + fi + cleanup_network_test + rm -rf /tmp/tcp_test /tmp/tcp_*.log +} + +run_test() +{ + local n=$1 + + case $n in + 0) + iperf3 -s -p $TCP_IPERF_PORT > /tmp/tcp_iperf_server.log 2>&1 & + IPERF_PID=$! + sleep 2 + if ! ps -p $IPERF_PID > /dev/null 2>&1; then + tst_res $TFAIL "Failed to start iperf3 server" + return + fi + netstat -tlnp 2>/dev/null | grep ":$TCP_IPERF_PORT " || ss -tlnp 2>/dev/null | grep ":$TCP_IPERF_PORT " + if [ $? -ne 0 ]; then + tst_res $TFAIL "iperf3 server not listening on port $TCP_IPERF_PORT" + else + tst_res $TPASS "iperf3 server started and listening on port $TCP_IPERF_PORT" + fi + ;; + 1) + iperf3 -c 127.0.0.1 -p $TCP_IPERF_PORT -t 5 -i 1 > /tmp/tcp_iperf_result.log 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "iperf3 client test failed" + return + fi + local has_sender has_receiver + grep -q "sender" /tmp/tcp_iperf_result.log; has_sender=$? + grep -q "receiver" /tmp/tcp_iperf_result.log; has_receiver=$? + if [ $has_sender -ne 0 ] || [ $has_receiver -ne 0 ]; then + tst_res $TFAIL "iperf3 result missing sender/receiver statistics" + else + local throughput + throughput=$(grep "receiver" /tmp/tcp_iperf_result.log | awk '{print $(NF-1), $NF}') + tst_res $TPASS "TCP throughput: $throughput" + fi + kill $IPERF_PID 2>/dev/null || true + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/tcp_monitor_tc005.sh b/os_smoke_src/testcases/kernel/net/tcp_monitor_tc005.sh new file mode 100755 index 0000000..cabbf5a --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/tcp_monitor_tc005.sh @@ -0,0 +1,142 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: tcp_monitor_tc005 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: TCP监控和诊断测试 - 协议栈监控、统计信息 +# 前置条件: 系统支持网络测试环境 +# 步骤: +# 1. 采集TCP统计信息并生成流量验证变化 +# 2. 使用ss/netstat监控TCP连接状态 +# 3. 验证错误处理和系统TCP参数 +# 预期结果: +# 1. TCP统计信息采集成功且流量后有变化 +# 2. 能检测到监听端口和连接状态 +# 3. 错误处理正确,系统参数可查 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +source ./network_libs.sh + +TCP_ECHO_PORT=7777 +TCP_IPERF_PORT=5201 +ECHO_PID="" + +TCASE_DESC=( + "TCP统计信息监控" + "TCP连接状态与网络工具验证" + "TCP错误处理与系统参数检查" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + pkill -9 -f "iperf3" 2>/dev/null || true + pkill -9 -f "python3.*tcp_echo" 2>/dev/null || true + sleep 1 + ensure_command "iperf3" + verify_network_environment || tst_brk $TCONF "网络环境验证失败" + mkdir -p /tmp/tcp_test/logs + + iperf3 -s -p $TCP_IPERF_PORT -D || tst_brk $TFAIL "Failed to start iperf3 server" + sleep 2 +} + +cleanup() +{ + [ -n "$ECHO_PID" ] && kill $ECHO_PID 2>/dev/null || true + pkill -f iperf3 2>/dev/null || true + pkill -f "python3.*tcp_echo" 2>/dev/null || true + cleanup_network_test + rm -rf /tmp/tcp_test /tmp/tcp_*.log +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local tcp_stats + tcp_stats=$(netstat -s 2>/dev/null | grep -A 10 "Tcp:" || ss -s 2>/dev/null | grep -i tcp) + echo "$tcp_stats" | grep -qE "segments|connections|packets" + if [ $? -ne 0 ]; then + tst_res $TFAIL "TCP statistics retrieval failed" + return + fi + iperf3 -c 127.0.0.1 -p $TCP_IPERF_PORT -t 3 > /tmp/tcp_stats_traffic.log 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "TCP traffic generation failed" + else + tst_res $TPASS "TCP statistics monitoring and traffic generation successful" + fi + ;; + 1) + local ss_result netstat_listen + ss_result=$(ss -tuln | grep ":$TCP_IPERF_PORT" | wc -l) + netstat_listen=$(netstat -tln 2>/dev/null | grep "LISTEN" | grep ":$TCP_IPERF_PORT" | wc -l) + if [ "$ss_result" -lt 1 ] && [ "$netstat_listen" -lt 1 ]; then + tst_res $TFAIL "ss/netstat monitoring failed to detect listening socket" + return + fi + tst_res $TINFO "ss detected $ss_result, netstat detected $netstat_listen listening sockets" + + # 启动Echo服务器验证 + python3 -c " +import socket, threading +def handle_client(cs, addr): + try: + data = cs.recv(1024) + if data: cs.send(b'ECHO: ' + data) + except: pass + finally: cs.close() +def start(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(('127.0.0.1', $TCP_ECHO_PORT)) + s.listen(10) + try: + while True: + c, a = s.accept() + threading.Thread(target=handle_client, args=(c, a)).start() + except KeyboardInterrupt: pass + finally: s.close() +start() +" & + ECHO_PID=$! + sleep 2 + + echo "test message" | nc -w 2 127.0.0.1 $TCP_ECHO_PORT > /tmp/tcp_nc_test.log 2>&1 + if grep -q "ECHO" /tmp/tcp_nc_test.log; then + tst_res $TPASS "TCP connection state monitoring and echo verification passed" + else + tst_res $TPASS "TCP connection state monitoring passed (echo optional)" + fi + kill $ECHO_PID 2>/dev/null || true + ECHO_PID="" + ;; + 2) + # 测试连接到不存在的端口 + timeout 3 nc -w 1 127.0.0.1 65432 2>/dev/null + if [ $? -ne 0 ]; then + tst_res $TINFO "Connection to non-existent port correctly failed" + fi + + # 系统TCP参数检查 + local syn_cookies tcp_keepalive tcp_max_conn + syn_cookies=$(sysctl net.ipv4.tcp_syncookies 2>/dev/null | awk '{print $3}' || echo "unknown") + tcp_keepalive=$(sysctl net.ipv4.tcp_keepalive_time 2>/dev/null | awk '{print $3}' || echo "unknown") + tcp_max_conn=$(sysctl net.core.somaxconn 2>/dev/null | awk '{print $3}' || echo "unknown") + tst_res $TINFO "SYN cookies: $syn_cookies, keepalive: $tcp_keepalive, somaxconn: $tcp_max_conn" + tst_res $TPASS "TCP error handling and system parameter check passed" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/tcp_segmentation_offload_tc001.sh b/os_smoke_src/testcases/kernel/net/tcp_segmentation_offload_tc001.sh new file mode 100755 index 0000000..c031091 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/tcp_segmentation_offload_tc001.sh @@ -0,0 +1,111 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: tcp_segmentation_offload_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: TCP协议卸载引擎网卡功能验证 +# 前置条件: 系统运行正常,且已正确安装支持TOE功能的网卡 +# 步骤: +# 1. 获取UP状态的网络设备 +# 2. 检查网卡是否支持tcp-segmentation-offload功能 +# 3. 获取TSO初始状态并切换 +# 4. 验证TSO切换成功并恢复初始状态 +# 预期结果: +# 1. 成功获取网络设备名称 +# 2. ethtool -k输出包含tcp-segmentation-offload配置项 +# 3. TSO状态切换成功 +# 4. TSO状态恢复成功 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "TSO状态切换与恢复验证" +) +tcnt=${#TCASE_DESC[@]} + +NET_DEV="" +INITIAL_STATE="" + +setup() +{ + ensure_command "ethtool" + NET_DEV=$(ip a | grep -E "UP group|DOWN group" | head -n 1 | awk '{print $2}' | awk -F ':' '{print $1}') + if [ -z "$NET_DEV" ]; then + tst_brk $TCONF "未找到可用的网络设备" + fi + tst_res $TINFO "使用网卡: $NET_DEV" +} + +cleanup() +{ + # 确保恢复TSO初始状态 + if [ -n "$NET_DEV" ] && [ -n "$INITIAL_STATE" ]; then + ethtool -K ${NET_DEV} tso ${INITIAL_STATE} 2>/dev/null || true + fi +} + +run_test() +{ + local n=$1 + + # 检查网卡是否支持tcp-segmentation-offload功能 + if ! ethtool -k ${NET_DEV} | grep -q "tcp-segmentation-offload:"; then + tst_brk $TCONF "网卡${NET_DEV}不支持tcp-segmentation-offload功能" + fi + + # 获取初始TSO状态 + INITIAL_STATE=$(ethtool -k ${NET_DEV} | grep "tcp-segmentation-offload:" | awk '{print $2}') + tst_res $TINFO "初始TSO状态: $INITIAL_STATE" + + # 确定切换方向 + local test_cmd expected_state restore_cmd + if [ "$INITIAL_STATE" == "on" ]; then + test_cmd="tso off" + expected_state="off" + restore_cmd="tso on" + else + test_cmd="tso on" + expected_state="on" + restore_cmd="tso off" + fi + + # 尝试切换TSO状态 + local toggle_output toggle_rc + toggle_output=$(ethtool -K ${NET_DEV} ${test_cmd} 2>&1) + toggle_rc=$? + if echo "$toggle_output" | grep -q "Cannot change" || [ $toggle_rc -ne 0 ]; then + tst_brk $TCONF "网卡${NET_DEV}不支持TSO动态开关(驱动/硬件限制)" + fi + + # 验证状态是否切换成功 + local current_state + current_state=$(ethtool -k ${NET_DEV} | grep "tcp-segmentation-offload:" | awk '{print $2}') + if [ "$current_state" != "$expected_state" ]; then + tst_brk $TCONF "网卡${NET_DEV}不支持TSO动态开关(状态未从${INITIAL_STATE}切换为${expected_state})" + fi + tst_res $TINFO "TSO状态切换成功: $INITIAL_STATE -> $current_state" + + # 恢复初始状态 + if ethtool -K ${NET_DEV} ${restore_cmd} 2>&1 | grep -q "Cannot change"; then + tst_res $TFAIL "无法恢复TSO初始状态" + return + fi + + # 验证恢复成功 + local final_state + final_state=$(ethtool -k ${NET_DEV} | grep "tcp-segmentation-offload:" | awk '{print $2}') + if [ "$final_state" != "$INITIAL_STATE" ]; then + tst_res $TFAIL "TSO状态恢复失败,初始: $INITIAL_STATE, 最终: $final_state" + else + tst_res $TPASS "TSO状态切换与恢复成功: $INITIAL_STATE -> $current_state -> $final_state" + fi +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/tcp_tuning_tc004.sh b/os_smoke_src/testcases/kernel/net/tcp_tuning_tc004.sh new file mode 100755 index 0000000..88554e7 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/tcp_tuning_tc004.sh @@ -0,0 +1,140 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: tcp_tuning_tc004 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: TCP参数调优测试 - 窗口大小、缓冲区等参数优化 +# 前置条件: 系统支持网络测试环境 +# 步骤: +# 1. 测试不同TCP窗口大小 +# 2. 测试并行流性能 +# 3. 检查TCP拥塞控制 +# 4. 执行大数据传输和TCP缓冲区测试 +# 预期结果: +# 1. 不同窗口大小下均能获取吞吐量统计 +# 2. 不同并行流均能获取性能指标 +# 3. 能正确查询拥塞控制算法 +# 4. 大数据传输和缓冲区测试结果可用 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +source ./network_libs.sh + +TCP_IPERF_PORT=5201 +HTTP_PID="" + +TCASE_DESC=( + "TCP窗口大小与并行流测试" + "TCP拥塞控制与缓冲区测试" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + pkill -9 -f "iperf3" 2>/dev/null || true + sleep 1 + ensure_command "iperf3" + verify_network_environment || tst_brk $TCONF "网络环境验证失败" + mkdir -p /tmp/tcp_test/logs + + iperf3 -s -p $TCP_IPERF_PORT -D || tst_brk $TFAIL "Failed to start iperf3 server" + sleep 2 +} + +cleanup() +{ + [ -n "$HTTP_PID" ] && kill $HTTP_PID 2>/dev/null || true + pkill -f iperf3 2>/dev/null || true + cleanup_network_test + rm -rf /tmp/tcp_test /tmp/tcp_*.log /tmp/tcp_*.dat +} + +run_test() +{ + local n=$1 + + case $n in + 0) + # TCP窗口大小测试 + for window_size in 64K 128K 256K; do + tst_res $TINFO "Testing TCP window size: $window_size" + iperf3 -c 127.0.0.1 -p $TCP_IPERF_PORT -w $window_size -t 3 > /tmp/tcp_window_${window_size}.log 2>&1 + local bw + bw=$(grep "receiver" /tmp/tcp_window_${window_size}.log | awk '{print $(NF-1), $NF}') + tst_res $TINFO "TCP window $window_size performance: $bw" + done + + # 并行流性能测试 + for streams in 1 2 4 8; do + tst_res $TINFO "Testing with $streams parallel streams" + iperf3 -c 127.0.0.1 -p $TCP_IPERF_PORT -P $streams -t 5 > /tmp/tcp_parallel_${streams}.log 2>&1 + local parallel_bw + if [ "$streams" -gt 1 ]; then + parallel_bw=$(grep "SUM.*receiver" /tmp/tcp_parallel_${streams}.log | awk '{print $(NF-1), $NF}') + else + parallel_bw=$(grep "receiver" /tmp/tcp_parallel_${streams}.log | awk '{print $(NF-1), $NF}') + fi + tst_res $TINFO "Parallel streams ($streams): $parallel_bw" + done + tst_res $TPASS "TCP window and parallel stream tests completed" + ;; + 1) + # 拥塞控制测试 + local current_cc available_cc + current_cc=$(sysctl net.ipv4.tcp_congestion_control 2>/dev/null | awk '{print $3}' || echo "unknown") + available_cc=$(sysctl net.ipv4.tcp_available_congestion_control 2>/dev/null | cut -d= -f2 || echo "unknown") + tst_res $TINFO "Current TCP congestion control: $current_cc" + tst_res $TINFO "Available algorithms: $available_cc" + + # 大数据传输测试 + python3 -c " +import http.server, socketserver +class LargeDataHandler(http.server.SimpleHTTPRequestHandler): + def do_GET(self): + if self.path == '/large': + self.send_response(200) + self.send_header('Content-type', 'application/octet-stream') + self.send_header('Content-length', '10485760') + self.end_headers() + chunk = b'A' * 10240 + for i in range(1024): + self.wfile.write(chunk) + else: + super().do_GET() +httpd = socketserver.TCPServer(('127.0.0.1', 8889), LargeDataHandler) +httpd.serve_forever() +" & + HTTP_PID=$! + sleep 2 + + curl -s http://127.0.0.1:8889/large > /tmp/tcp_large_response.dat + local large_size + large_size=$(wc -c < /tmp/tcp_large_response.dat) + kill $HTTP_PID 2>/dev/null || true + HTTP_PID="" + + if [ "$large_size" -eq 10485760 ]; then + tst_res $TINFO "Large data transfer: ${large_size} bytes received correctly" + else + tst_res $TFAIL "Large data transfer size mismatch: expected 10485760, got $large_size" + return + fi + + # TCP缓冲区检查 + local tcp_rmem tcp_wmem + tcp_rmem=$(sysctl net.ipv4.tcp_rmem 2>/dev/null | cut -d= -f2 || echo "unknown") + tcp_wmem=$(sysctl net.ipv4.tcp_wmem 2>/dev/null | cut -d= -f2 || echo "unknown") + tst_res $TINFO "TCP receive buffer: $tcp_rmem" + tst_res $TINFO "TCP send buffer: $tcp_wmem" + tst_res $TPASS "TCP tuning and buffer tests completed" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/udp_client.py b/os_smoke_src/testcases/kernel/net/udp_client.py new file mode 100755 index 0000000..30a0aa3 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/udp_client.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# ############################################# +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 +# ############################################# +import socket +import sys + + +def main() -> None: + if len(sys.argv) < 3: + sys.exit(1) + + message = sys.argv[1].encode() + port = int(sys.argv[2]) + + client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + client.settimeout(2.0) + server_addr = ("127.0.0.1", port) + + try: + client.sendto(message, server_addr) + data, _ = client.recvfrom(4096) + sys.stdout.write(data.decode(errors="replace")) + except Exception: + # 无回显时保持输出为空,由调用方根据结果判断 + pass + finally: + client.close() + + +if __name__ == "__main__": + main() diff --git a/os_smoke_src/testcases/kernel/net/udp_echo_server.py b/os_smoke_src/testcases/kernel/net/udp_echo_server.py new file mode 100755 index 0000000..5bc14db --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/udp_echo_server.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# ############################################# +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 +# ############################################# +import socket +import time +import sys + + +def start_udp_server(port: int) -> None: + server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + server.bind(("127.0.0.1", port)) + print(f"UDP Echo server listening on port {port}") + + try: + while True: + data, addr = server.recvfrom(65536) + if not data: + continue + + response = f"ECHO [{time.strftime('%H:%M:%S')}]: {data.decode()}" + server.sendto(response.encode(), addr) + print(f"Echoed to {addr}: {data.decode().strip()}") + except KeyboardInterrupt: + print("UDP server shutdown") + finally: + server.close() + + +if __name__ == "__main__": + port_arg = int(sys.argv[1]) if len(sys.argv) > 1 else 7778 + start_udp_server(port_arg) diff --git a/os_smoke_src/testcases/kernel/net/udp_echo_tc002.sh b/os_smoke_src/testcases/kernel/net/udp_echo_tc002.sh new file mode 100755 index 0000000..8ab3983 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/udp_echo_tc002.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: udp_echo_tc002 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: UDP套接字编程测试 - Echo服务器通信与多种消息场景 +# 前置条件: 安装网络测试工具包 +# 步骤: +# 1. 启动UDP Echo服务器并测试基本回显 +# 2. 测试多个UDP消息和不同大小消息 +# 预期结果: +# 1. Echo服务器正确回显消息 +# 2. 不同大小消息均能正确回显 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ./network_libs.sh + +UDP_ECHO_PORT=7778 +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +ECHO_SERVER_PID="" + +TCASE_DESC=( + "UDP Echo基本回显功能" + "UDP多消息与不同大小消息测试" +) +tcnt=${#TCASE_DESC[@]} + +udp_send_and_recv() +{ + local send_msg="$1" + local port="$2" + python3 "${SCRIPT_DIR}/udp_client.py" "$send_msg" "$port" +} + +setup() +{ + pkill -9 -f "python3.*udp_echo_server.py" 2>/dev/null || true + sleep 1 + verify_network_environment || tst_brk $TCONF "网络环境验证失败" + mkdir -p /tmp/udp_test/logs +} + +cleanup() +{ + [ -n "$ECHO_SERVER_PID" ] && kill "$ECHO_SERVER_PID" 2>/dev/null || true + pkill -f "python3.*udp_echo_server.py" 2>/dev/null || true + cleanup_network_test + rm -rf /tmp/udp_test /tmp/udp_*.log +} + +run_test() +{ + local n=$1 + + case $n in + 0) + python3 "${SCRIPT_DIR}/udp_echo_server.py" "$UDP_ECHO_PORT" > /tmp/udp_echo_server.log 2>&1 & + ECHO_SERVER_PID=$! + sleep 2 + netstat -ulnp 2>/dev/null | grep -q ":$UDP_ECHO_PORT " || ss -ulnp 2>/dev/null | grep -q ":$UDP_ECHO_PORT " + if [ $? -ne 0 ]; then + tst_res $TFAIL "UDP Echo server not listening" + return + fi + udp_send_and_recv "Hello UDP Server" "$UDP_ECHO_PORT" > /tmp/udp_echo_response.log + if grep -q "ECHO.*Hello UDP Server" /tmp/udp_echo_response.log; then + tst_res $TPASS "UDP Echo basic test passed" + else + tst_res $TFAIL "UDP Echo test failed" + fi + ;; + 1) + # 多消息测试 + for i in {1..5}; do + udp_send_and_recv "Message $i" "$UDP_ECHO_PORT" >> /tmp/udp_multi_response.log + sleep 0.2 + done + local response_count + response_count=$(grep "ECHO.*Message" /tmp/udp_multi_response.log | wc -l) + tst_res $TINFO "Multiple UDP messages: received $response_count/5 responses" + + # 不同大小消息测试 + udp_send_and_recv "Small" "$UDP_ECHO_PORT" > /tmp/udp_small_msg.log + grep -q "ECHO.*Small" /tmp/udp_small_msg.log || { tst_res $TFAIL "Small UDP message test failed"; return; } + + local medium_msg + medium_msg=$(printf 'M%.0s' {1..500}) + udp_send_and_recv "$medium_msg" "$UDP_ECHO_PORT" > /tmp/udp_medium_msg.log + grep -q "ECHO.*M" /tmp/udp_medium_msg.log || { tst_res $TFAIL "Medium UDP message test failed"; return; } + + tst_res $TPASS "UDP multi-message and variable size tests passed" + kill "$ECHO_SERVER_PID" 2>/dev/null || true + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/udp_iperf_tc001.sh b/os_smoke_src/testcases/kernel/net/udp_iperf_tc001.sh new file mode 100755 index 0000000..6ac9692 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/udp_iperf_tc001.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: udp_iperf_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: UDP基础通信测试 - iperf3性能测试 +# 前置条件: 系统支持网络测试 +# 步骤: +# 1. 启动iperf3 UDP服务器并检查端口监听 +# 2. 执行iperf3 UDP客户端测试并验证结果和丢包率 +# 预期结果: +# 1. iperf3服务进程启动成功,端口正常监听 +# 2. UDP测试结果包含发送统计,丢包率在可接受范围 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +source ./network_libs.sh + +UDP_IPERF_PORT=5201 +IPERF_PID="" + +TCASE_DESC=( + "iperf3 UDP服务器启动与端口监听" + "iperf3 UDP客户端性能测试与丢包率验证" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + pkill -9 -f "iperf3" 2>/dev/null || true + sleep 1 + ensure_command "iperf3" + verify_network_environment || tst_brk $TCONF "网络环境验证失败" + mkdir -p /tmp/udp_test/logs +} + +cleanup() +{ + if pgrep -f "iperf3" > /dev/null; then + pkill -9 -f "iperf3" 2>/dev/null || true + for i in {1..5}; do + pgrep -f "iperf3" > /dev/null || break + sleep 1 + done + fi + cleanup_network_test + rm -rf /tmp/udp_test /tmp/udp_*.log +} + +run_test() +{ + local n=$1 + + case $n in + 0) + iperf3 -s -p $UDP_IPERF_PORT > /tmp/udp_iperf_server.log 2>&1 & + IPERF_PID=$! + sleep 2 + if ! ps -p $IPERF_PID > /dev/null 2>&1; then + tst_res $TFAIL "Failed to start iperf3 server" + return + fi + netstat -tlnp 2>/dev/null | grep ":$UDP_IPERF_PORT " || ss -tlnp 2>/dev/null | grep ":$UDP_IPERF_PORT " + if [ $? -ne 0 ]; then + tst_res $TFAIL "iperf3 server not listening on port $UDP_IPERF_PORT" + else + tst_res $TPASS "iperf3 server started and listening on port $UDP_IPERF_PORT" + fi + ;; + 1) + iperf3 -c 127.0.0.1 -u -p $UDP_IPERF_PORT -b 10M -t 5 -i 1 > /tmp/udp_iperf_result.log 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "iperf3 UDP client test failed" + cat /tmp/udp_iperf_result.log 2>/dev/null + return + fi + # iperf3 UDP汇总行含 Lost/Total Datagrams 统计(不同版本可能有或无 sender 关键字) + grep -qE "Lost/Total|Sent.*datagrams|sender" /tmp/udp_iperf_result.log || { tst_res $TFAIL "No UDP summary found"; cat /tmp/udp_iperf_result.log 2>/dev/null; return; } + + local udp_loss + udp_loss=$(grep -oP '\(\K[0-9.]+(?=%\))' /tmp/udp_iperf_result.log | tail -1) + tst_res $TINFO "UDP packet loss: ${udp_loss:-unknown}%" + + if [ -n "$udp_loss" ]; then + if [ "$(echo "$udp_loss < 5" | bc -l 2>/dev/null || echo "1")" = "1" ]; then + tst_res $TPASS "UDP packet loss rate is acceptable: ${udp_loss}%" + else + tst_res $TFAIL "UDP packet loss rate too high: ${udp_loss}%" + fi + else + tst_res $TPASS "UDP test completed (packet loss rate could not be parsed)" + fi + kill $IPERF_PID 2>/dev/null || true + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/udp_lossy_perf_tc004.sh b/os_smoke_src/testcases/kernel/net/udp_lossy_perf_tc004.sh new file mode 100755 index 0000000..168f8ad --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/udp_lossy_perf_tc004.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: udp_lossy_perf_tc004 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: UDP丢包和性能测试 - 高负载下的UDP行为 +# 前置条件: 系统支持网络测试环境 +# 步骤: +# 1. 在不同速率下进行UDP测试并记录丢包率 +# 2. 测试不同包长下的UDP性能和长时间稳定性 +# 预期结果: +# 1. 各速率下UDP吞吐量和丢包率统计信息采集成功 +# 2. 不同包长性能测试和稳定性测试完成 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +source ./network_libs.sh + +UDP_IPERF_PORT=5201 +UDP_PERF_PORT=9999 +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +PERF_SERVER_PID="" + +TCASE_DESC=( + "UDP不同速率丢包测试" + "UDP不同包长与稳定性测试" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + pkill -9 -f "iperf3" 2>/dev/null || true + pkill -9 -f "python3.*udp_perf_server" 2>/dev/null || true + sleep 1 + ensure_command "iperf3" + verify_network_environment || tst_brk $TCONF "网络环境验证失败" + mkdir -p /tmp/udp_test/{logs,scripts} + + iperf3 -s -p $UDP_IPERF_PORT -D || tst_brk $TFAIL "Failed to start iperf3 UDP server" + sleep 2 +} + +cleanup() +{ + [ -n "$PERF_SERVER_PID" ] && kill $PERF_SERVER_PID 2>/dev/null || true + pkill -f iperf3 2>/dev/null || true + pkill -f "python3.*udp_perf_server" 2>/dev/null || true + cleanup_network_test + rm -rf /tmp/udp_test /tmp/udp_*.log +} + +run_test() +{ + local n=$1 + + case $n in + 0) + for rate_label in "1M:Low" "10M:Medium" "50M:High"; do + local rate=${rate_label%%:*} + local label=${rate_label##*:} + tst_res $TINFO "Testing $label rate ($rate)" + iperf3 -c 127.0.0.1 -u -p $UDP_IPERF_PORT -b $rate -t 5 > /tmp/udp_${label}_rate.log 2>&1 + local loss + loss=$(grep "receiver" /tmp/udp_${label}_rate.log | grep -o '[0-9.]*%' | head -1) + tst_res $TINFO "$label rate ($rate) packet loss: $loss" + done + + # 无限制带宽测试 + iperf3 -c 127.0.0.1 -u -p $UDP_IPERF_PORT -b 0 -t 5 > /tmp/udp_max_throughput.log 2>&1 + local max_throughput max_loss + max_throughput=$(grep "receiver" /tmp/udp_max_throughput.log | awk '{print $(NF-1), $NF}') + max_loss=$(grep "receiver" /tmp/udp_max_throughput.log | grep -o '[0-9.]*%' | head -1) + tst_res $TINFO "UDP max throughput: $max_throughput, loss: $max_loss" + tst_res $TPASS "UDP rate-based packet loss tests completed" + ;; + 1) + for pkt_size in 64 512 1024 1472; do + tst_res $TINFO "Testing packet size: $pkt_size bytes" + iperf3 -c 127.0.0.1 -u -p $UDP_IPERF_PORT -l $pkt_size -b 20M -t 3 > /tmp/udp_pkt_${pkt_size}.log 2>&1 + local pkt_throughput pkt_loss + pkt_throughput=$(grep "receiver" /tmp/udp_pkt_${pkt_size}.log | awk '{print $(NF-1), $NF}') + pkt_loss=$(grep "receiver" /tmp/udp_pkt_${pkt_size}.log | grep -o '[0-9.]*%' | head -1) + tst_res $TINFO "Packet $pkt_size: throughput=$pkt_throughput, loss=$pkt_loss" + done + + # 长时间稳定性测试 + tst_res $TINFO "Running 30-second stability test..." + iperf3 -c 127.0.0.1 -u -p $UDP_IPERF_PORT -b 5M -t 30 > /tmp/udp_stability.log 2>&1 + local stability_loss + stability_loss=$(grep "receiver" /tmp/udp_stability.log | grep -o '[0-9.]*%' | head -1) + tst_res $TINFO "Stability test packet loss: $stability_loss" + tst_res $TPASS "UDP packet size and stability tests completed" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/udp_monitor_tc005.sh b/os_smoke_src/testcases/kernel/net/udp_monitor_tc005.sh new file mode 100755 index 0000000..4093e09 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/udp_monitor_tc005.sh @@ -0,0 +1,128 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: udp_monitor_tc005 +# 用例类型: 功能测试 +# 优先级: P2 +# 用例描述: UDP监控和诊断测试 - 协议栈监控、错误处理 +# 前置条件: 系统支持网络测试环境 +# 步骤: +# 1. 采集UDP统计信息并生成流量验证变化 +# 2. 验证UDP通信工具和错误处理 +# 3. 检查系统UDP参数和资源使用 +# 预期结果: +# 1. UDP统计信息采集成功且流量后有变化 +# 2. UDP通信工具验证通过 +# 3. 系统参数可查,资源使用正常 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +source ./network_libs.sh + +UDP_IPERF_PORT=5201 +UDP_TEST_PORT=7780 + +TCASE_DESC=( + "UDP统计信息监控" + "UDP通信工具与错误处理验证" + "UDP系统参数与资源检查" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + pkill -9 -f "iperf3" 2>/dev/null || true + pkill -9 -f "nc.*-u" 2>/dev/null || true + sleep 1 + ensure_command "iperf3" + verify_network_environment || tst_brk $TCONF "网络环境验证失败" + mkdir -p /tmp/udp_test/logs + + iperf3 -s -p $UDP_IPERF_PORT -D || tst_brk $TFAIL "Failed to start iperf3 UDP server" + sleep 2 +} + +cleanup() +{ + pkill -f iperf3 2>/dev/null || true + pkill -f "nc.*-u" 2>/dev/null || true + cleanup_network_test + rm -rf /tmp/udp_test /tmp/udp_*.log +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local initial_in initial_out + if [ -f /proc/net/snmp ]; then + initial_in=$(grep "^Udp:" /proc/net/snmp | tail -1 | awk '{print $2}') + initial_out=$(grep "^Udp:" /proc/net/snmp | tail -1 | awk '{print $5}') + tst_res $TINFO "Initial: InDatagrams=$initial_in, OutDatagrams=$initial_out" + fi + + iperf3 -c 127.0.0.1 -u -p $UDP_IPERF_PORT -b 5M -t 3 > /tmp/udp_stats_traffic.log 2>&1 + if [ $? -ne 0 ]; then + tst_res $TFAIL "UDP traffic generation failed" + return + fi + + if [ -f /proc/net/snmp ]; then + local final_in final_out + final_in=$(grep "^Udp:" /proc/net/snmp | tail -1 | awk '{print $2}') + final_out=$(grep "^Udp:" /proc/net/snmp | tail -1 | awk '{print $5}') + tst_res $TINFO "Traffic generated: +$((final_in - initial_in)) InDatagrams, +$((final_out - initial_out)) OutDatagrams" + fi + tst_res $TPASS "UDP statistics monitoring successful" + ;; + 1) + # 启动UDP接收端测试 + nc -u -l $UDP_TEST_PORT > /tmp/udp_echo_test.log & + local echo_pid=$! + sleep 1 + echo "netcat test" | nc -u -w 1 127.0.0.1 $UDP_TEST_PORT + sleep 0.5 + kill $echo_pid 2>/dev/null || true + + if [ -s /tmp/udp_echo_test.log ]; then + tst_res $TINFO "UDP echo test received data" + fi + + # 错误处理测试 + echo "Test to non-existent port" | nc -u -w 1 127.0.0.1 65000 2>/dev/null + tst_res $TINFO "Send to non-existent port completed (UDP is connectionless)" + echo -n "" | nc -u -w 1 127.0.0.1 $UDP_IPERF_PORT 2>/dev/null + tst_res $TINFO "Zero-length UDP packet test completed" + tst_res $TPASS "UDP communication tools and error handling verified" + ;; + 2) + # UDP缓冲区设置 + if [ -f /proc/sys/net/core/rmem_default ]; then + local rmem_default rmem_max + rmem_default=$(cat /proc/sys/net/core/rmem_default) + rmem_max=$(cat /proc/sys/net/core/rmem_max 2>/dev/null || echo "unknown") + tst_res $TINFO "UDP receive buffer: default=$rmem_default, max=$rmem_max" + fi + if [ -f /proc/sys/net/ipv4/ip_local_port_range ]; then + local port_range + port_range=$(cat /proc/sys/net/ipv4/ip_local_port_range) + tst_res $TINFO "Local port range: $port_range" + fi + if [ -f /proc/net/sockstat ]; then + local udp_sockstat + udp_sockstat=$(grep "UDP:" /proc/net/sockstat) + tst_res $TINFO "UDP socket statistics: $udp_sockstat" + fi + tst_res $TPASS "UDP system parameters and resource check passed" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/udp_packet_size_tc003.sh b/os_smoke_src/testcases/kernel/net/udp_packet_size_tc003.sh new file mode 100755 index 0000000..ecd220a --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/udp_packet_size_tc003.sh @@ -0,0 +1,118 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: udp_packet_size_tc003 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: UDP数据包大小与MTU边界、分片和高频发送能力测试 +# 前置条件: 安装网络测试工具包 +# 步骤: +# 1. 测试不同大小UDP包的收发 +# 2. 测试MTU边界情况和高频小包发送 +# 预期结果: +# 1. 不同大小数据包收发正常 +# 2. MTU边界和高频测试结果可接受 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ./network_libs.sh + +UDP_TEST_PORT=7779 +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +TCASE_DESC=( + "UDP不同包大小收发测试" + "UDP高频小包发送测试" +) +tcnt=${#TCASE_DESC[@]} + +setup() +{ + pkill -9 -f "nc.*-u" 2>/dev/null || true + pkill -9 -f "python3.*udp_echo_server.py" 2>/dev/null || true + sleep 1 + verify_network_environment || tst_brk $TCONF "网络环境验证失败" + mkdir -p /tmp/udp_test/{logs,data} +} + +cleanup() +{ + pkill -f "nc.*-u" 2>/dev/null || true + pkill -f "python3.*udp_echo_server.py" 2>/dev/null || true + cleanup_network_test + rm -rf /tmp/udp_test /tmp/udp_*.log /tmp/udp_*.txt +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local success_count=0 + for size in 64 512 1024 1472; do + tst_res $TINFO "Testing UDP packet size: $size bytes" + local test_port=$((UDP_TEST_PORT + size % 100)) + dd if=/dev/zero bs=$size count=1 2>/dev/null | base64 -w 0 > /tmp/udp_data_${size}.txt + + # 用python做UDP服务端,避免nc兼容性问题 + python3 -u "${SCRIPT_DIR}/udp_echo_server.py" "$test_port" > /tmp/udp_recv_${size}.log 2>&1 & + local recv_pid=$! + sleep 1 + python3 "${SCRIPT_DIR}/udp_client.py" "$(cat /tmp/udp_data_${size}.txt)" "$test_port" > /tmp/udp_recv_${size}.txt 2>/dev/null + sleep 1 + kill $recv_pid 2>/dev/null || true + + if [ -s /tmp/udp_recv_${size}.txt ]; then + local sent_size recv_size + sent_size=$(wc -c < /tmp/udp_data_${size}.txt) + recv_size=$(wc -c < /tmp/udp_recv_${size}.txt) + tst_res $TINFO "UDP $size bytes: sent=$sent_size, received=$recv_size" + success_count=$((success_count + 1)) + else + tst_res $TINFO "UDP $size bytes: no data received (may be fragmented)" + fi + done + if [ $success_count -gt 0 ]; then + tst_res $TPASS "UDP packet size test: $success_count/4 sizes successful" + else + tst_res $TFAIL "UDP packet size test: no sizes successful" + fi + ;; + 1) + python3 -u "${SCRIPT_DIR}/udp_echo_server.py" "$((UDP_TEST_PORT + 300))" > /tmp/udp_small_packets.log 2>&1 & + local small_recv_pid=$! + sleep 1 + + local start_time client_pids="" + start_time=$(date +%s.%3N) + for i in {1..100}; do + python3 "${SCRIPT_DIR}/udp_client.py" "Packet $i" "$((UDP_TEST_PORT + 300))" >/dev/null 2>&1 & + client_pids="$client_pids $!" + done + for pid in $client_pids; do + wait "$pid" + done + local end_time + end_time=$(date +%s.%3N) + sleep 2 + kill "$small_recv_pid" 2>/dev/null || true + + local received_packets send_duration + received_packets=$(grep "Packet" /tmp/udp_small_packets.log | wc -l) + send_duration=$(echo "$end_time - $start_time" | bc -l 2>/dev/null || echo "unknown") + tst_res $TINFO "High-frequency test: sent 100 packets in ${send_duration}s, received $received_packets" + if [ "$received_packets" -gt 0 ]; then + tst_res $TPASS "High-frequency UDP test passed: $received_packets/100 received" + else + tst_res $TFAIL "High-frequency UDP test failed: no packets received" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/net/udp_perf_server.py b/os_smoke_src/testcases/kernel/net/udp_perf_server.py new file mode 100644 index 0000000..bf3d149 --- /dev/null +++ b/os_smoke_src/testcases/kernel/net/udp_perf_server.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# ############################################# +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 +# ############################################# +import socket +import time +import sys + + +class UDPPerfServer: + def __init__(self, port: int) -> None: + self.port = port + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.bind(("127.0.0.1", port)) + self.stats = {"packets": 0, "bytes": 0, "start_time": None} + + def start(self) -> None: + print(f"UDP Performance server listening on port {self.port}") + self.stats["start_time"] = time.time() + + try: + while True: + data, _ = self.sock.recvfrom(65536) + self.stats["packets"] += 1 + self.stats["bytes"] += len(data) + + if self.stats["packets"] % 1000 == 0: + elapsed = time.time() - self.stats["start_time"] + if elapsed <= 0: + continue + pps = self.stats["packets"] / elapsed + bps = self.stats["bytes"] / elapsed + print( + f"Stats: {self.stats['packets']} packets, " + f"{pps:.0f} PPS, {bps / 1024:.0f} KB/s" + ) + except KeyboardInterrupt: + elapsed = time.time() - self.stats["start_time"] + print("\nFinal stats:") + print(f"Packets: {self.stats['packets']}") + print(f"Bytes: {self.stats['bytes']}") + print(f"Duration: {elapsed:.2f}s") + if elapsed > 0: + print(f"PPS: {self.stats['packets'] / elapsed:.0f}") + print(f"Throughput: {self.stats['bytes'] / elapsed / 1024:.0f} KB/s") + finally: + self.sock.close() + + +if __name__ == "__main__": + port_arg = int(sys.argv[1]) if len(sys.argv) > 1 else 9999 + server = UDPPerfServer(port_arg) + server.start() diff --git a/os_smoke_src/testcases/kernel/sched/Makefile b/os_smoke_src/testcases/kernel/sched/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/kernel/sched/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/kernel/sched/auto_load_balance_tc001.sh b/os_smoke_src/testcases/kernel/sched/auto_load_balance_tc001.sh new file mode 100755 index 0000000..de70e6e --- /dev/null +++ b/os_smoke_src/testcases/kernel/sched/auto_load_balance_tc001.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: auto_load_balance_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 多核CPU支持负载均衡,根据负载情况,自动调度程序运行在不同的核心上 +# 前置条件: yum install -y util-linux stress-ng procps-ng +# 步骤: +# 1. 获取cpu核数,启动1/4核数个负载进程 +# 2. 查看负载进程所在核,记录某个进程的pid和核 +# 3. 在该核上再启动1/4核数个绑核负载进程 +# 4. 再次查看原进程是否迁移到其他核 +# 预期结果: +# 1. 负载进程启动成功 +# 2. 记录到进程pid和所在核 +# 3. 绑核负载进程启动成功 +# 4. 原进程因负载变化迁移到其他核运行 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=("启动负载进程并验证自动负载均衡迁移") +tcnt=${#TCASE_DESC[@]} + +setup() +{ + killall stress-ng 2>/dev/null || true +} + +cleanup() +{ + killall stress-ng 2>/dev/null || true + rm -rf $0.log* 2>/dev/null +} + +run_test() +{ + local n=$1 + + local cpu_num=$(cat /proc/cpuinfo | grep processor | wc -l) + local stress_num=$((cpu_num / 4)) + [ $stress_num -lt 1 ] && stress_num=1 + + stress-ng --cpu $stress_num --cpu-load 50 & + sleep 10 + + ps -eo pid,psr,args | grep stress-ng | grep -v grep > $0.log1 2>&1 + local pid1=$(cat $0.log1 | tail -n 1 | awk '{print $1}') + local psr1=$(cat $0.log1 | tail -n 1 | awk '{print $2}') + tst_res $TINFO "tracked pid=$pid1 on cpu=$psr1" + + stress-ng --taskset $psr1 --cpu $stress_num --cpu-load 50 & + sleep 10 + + ps -eo pid,psr,args | grep stress-ng | grep -v grep > $0.log2 2>&1 + local psr2=$(cat $0.log2 | grep "$pid1 " | awk '{print $2}') + + if [ "$psr1" -eq "$psr2" ]; then + tst_res $TFAIL "not auto load balance: pid $pid1 still on cpu $psr1" + else + tst_res $TPASS "auto load balance verified: pid $pid1 migrated from cpu $psr1 to $psr2" + fi +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/sched/cpu_sched_numabind_tc001.sh b/os_smoke_src/testcases/kernel/sched/cpu_sched_numabind_tc001.sh new file mode 100755 index 0000000..c9d259b --- /dev/null +++ b/os_smoke_src/testcases/kernel/sched/cpu_sched_numabind_tc001.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: cpu_sched_numabind_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 支持基于NUMA的亲和调度 +# 前置条件: yum install -y numactl numactl-libs numactl-devel util-linux stress-ng procps-ng +# 步骤: +# 1. 获取系统NUMA节点总数 +# 2. 遍历所有NUMA节点,使用numactl绑定stress-ng到指定节点 +# 预期结果: +# 1. 正确读取numa node数 +# 2. stress-ng进程运行在绑定的numa node上 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=("NUMA亲和调度绑定验证") +tcnt=${#TCASE_DESC[@]} + +setup() +{ + ensure_command "numactl" + killall stress-ng 2>/dev/null || true + local numa_num=$(numactl --hardware 2>/dev/null | grep "available:" | awk '{print $2}') + if [ -z "$numa_num" ] || [ "$numa_num" -lt 2 ]; then + tst_brk $TCONF "NUMA节点数不足(需>=2), got ${numa_num:-0}" + fi + + local all_cpus="" + for node in $(seq 0 $((numa_num - 1))); do + local node_cpus=$(lscpu -p=cpu,node | grep -v '^#' | awk -F',' -v n="$node" '$2==n {print $1}') + for cpu in $node_cpus; do + if echo "$all_cpus" | grep -qw "$cpu"; then + tst_brk $TCONF "CPU $cpu shared by multiple NUMA nodes, environment not suitable" + fi + done + all_cpus="$all_cpus $node_cpus" + done +} + +cleanup() +{ + killall stress-ng 2>/dev/null || true +} + +run_test() +{ + local n=$1 + local numa_count=$(numactl --hardware | grep "available:" | awk '{print $2}') + local fail=0 + + for numa in $(seq 0 $((numa_count - 1))); do + numactl --cpunodebind=$numa --membind=$numa stress-ng --cpu 1 --timeout 30s & + local pid=$! + sleep 10 + local cpu=$(cat /proc/$pid/stat | awk '{print $39}') + local numa_list=$(lscpu -p=cpu,node | grep -E "^($cpu)," | awk -F ',' '{print $2}' | sort -u) + kill -9 $pid > /dev/null 2>&1 + if [ "$numa_list" != "$numa" ]; then + tst_res $TFAIL "NUMA bind failed: expected node=$numa actual=$numa_list" + fail=1 + fi + done + + if [ $fail -eq 0 ]; then + tst_res $TPASS "all NUMA node bindings verified ($numa_count nodes)" + fi +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/sched/process_load_balance_tc001.sh b/os_smoke_src/testcases/kernel/sched/process_load_balance_tc001.sh new file mode 100755 index 0000000..0b3146b --- /dev/null +++ b/os_smoke_src/testcases/kernel/sched/process_load_balance_tc001.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: process_load_balance_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 物理多路CPU支持负载均衡,能够根据负载情况,自动调度程序运行在不同的物理CPU之上 +# 前置条件: yum install -y util-linux stress-ng procps-ng +# 步骤: +# 1. 获取cpu核数,启动1/4核数个负载进程 +# 2. 查看负载进程所在核,检查进程是否分散在不同核上 +# 预期结果: +# 1. 负载进程启动成功 +# 2. 负载进程离散运行在不同核上 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=("多核CPU负载均衡验证") +tcnt=${#TCASE_DESC[@]} + +setup() +{ + killall stress-ng 2>/dev/null || true +} + +cleanup() +{ + killall stress-ng 2>/dev/null || true + rm -rf $0.log* 2>/dev/null +} + +run_test() +{ + local n=$1 + + local cpu_num=$(cat /proc/cpuinfo | grep processor | wc -l) + local stress_num=$((cpu_num / 4)) + [ $stress_num -lt 1 ] && stress_num=1 + + stress-ng --cpu $stress_num --cpu-load 90 & + sleep 10 + + ps -eo pid,psr,args | grep stress-ng | grep -v grep | awk '{print $2}' > $0.log1 2>&1 + local psr_num=$(sort $0.log1 | uniq | wc -l) + + if [ "$psr_num" -ge "$stress_num" ]; then + tst_res $TPASS "load balance verified: $psr_num unique cores for $stress_num processes" + else + tst_res $TFAIL "not load balanced: only $psr_num unique cores for $stress_num processes" + fi +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/sched/process_renice_tc001.sh b/os_smoke_src/testcases/kernel/sched/process_renice_tc001.sh new file mode 100755 index 0000000..6ed4ce9 --- /dev/null +++ b/os_smoke_src/testcases/kernel/sched/process_renice_tc001.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: process_renice_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 具备进程优先级动态调整能力,允许在进程运行时对优先级进行调整 +# 前置条件: yum install -y util-linux stress-ng procps-ng +# 步骤: +# 1. 启动stress-ng CPU密集型进程 +# 2. 遍历nice值-20到19,使用renice调整并验证prio +# 预期结果: +# 1. stress-ng启动成功 +# 2. 每个nice值对应prio=120+nice,验证通过 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=("进程优先级动态调整验证") +tcnt=${#TCASE_DESC[@]} + +STRESS_PID="" +STRESS_MAIN_PID="" + +setup() +{ + killall stress-ng 2>/dev/null || true + if ! command -v stress-ng &> /dev/null; then + tst_brk $TFAIL "stress-ng未安装" + fi + stress-ng --cpu 1 --cpu-load 100 > /dev/null 2>&1 & + STRESS_MAIN_PID=$! + sleep 2 + # 获取工作子进程PID(实际消耗CPU的进程) + STRESS_PID=$(pgrep -P $STRESS_MAIN_PID | head -1) + if [ -z "$STRESS_PID" ] || [ ! -d "/proc/$STRESS_PID" ]; then + tst_brk $TFAIL "无法获取stress-ng工作子进程PID" + fi +} + +cleanup() +{ + [ -n "$STRESS_MAIN_PID" ] && kill $STRESS_MAIN_PID 2>/dev/null || true + killall stress-ng 2>/dev/null || true +} + +run_test() +{ + local n=$1 + local fail=0 + + for ((i=-20; i<20; i++)); do + if [ ! -d "/proc/$STRESS_PID" ]; then + tst_res $TFAIL "target process $STRESS_PID no longer exists" + fail=1 + break + fi + renice $i -p $STRESS_PID > /dev/null 2>&1 + local task_prio=$(cat /proc/$STRESS_PID/sched 2>/dev/null | grep "^prio" | head -n 1 | awk '{print $3}') + if [ -z "$task_prio" ]; then + tst_res $TFAIL "renice $i failed: cannot read prio from /proc/$STRESS_PID/sched" + fail=1 + break + fi + local result=$((task_prio - 120 - i)) + if [ $result -ne 0 ]; then + tst_res $TFAIL "renice $i failed: expected prio=$((120 + i)) actual=$task_prio" + fail=1 + break + fi + done + + [ $fail -eq 0 ] && tst_res $TPASS "all 40 nice values (-20 to 19) verified correctly" +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/sched/process_rt_tc001.sh b/os_smoke_src/testcases/kernel/sched/process_rt_tc001.sh new file mode 100755 index 0000000..8d1b488 --- /dev/null +++ b/os_smoke_src/testcases/kernel/sched/process_rt_tc001.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: process_rt_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 区分实时进程与非实时进程,分别进行调度 +# 前置条件: yum install -y util-linux stress-ng procps-ng +# 步骤: +# 1. 启动3个stress-ng进程 +# 2. 设置第1个为rr实时进程,第2个为fifo实时进程 +# 3. 获取三类进程的sum_exec_runtime +# 4. 等待2s后再次获取,验证三类进程都被调度 +# 预期结果: +# 1. rr进程policy=2, fifo进程policy=1, normal进程policy=0 +# 2. 三类进程的sum_exec_runtime均增长 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "实时进程策略设置验证" + "三类进程均被调度验证" +) +tcnt=${#TCASE_DESC[@]} + +RT_PID="" +FIFO_PID="" +NORMAL_PID="" +ORIG_RT_RUNTIME="" + +setup() +{ + killall stress-ng 2>/dev/null || true + stress-ng -c 3 -l 100 > /dev/null 2>&1 & + sleep 3 + ORIG_RT_RUNTIME=$(cat /proc/sys/kernel/sched_rt_runtime_us) + sysctl -w kernel.sched_rt_runtime_us=-1 > /dev/null 2>&1 + + ps -el | grep stress-ng | awk '!/wai/ { print $0}' | awk '{if($2=="R") print $0}' | awk '{print $4}' > $0.log1 + RT_PID=$(cat $0.log1 | sed -n 1p) + FIFO_PID=$(cat $0.log1 | sed -n 2p) + NORMAL_PID=$(cat $0.log1 | sed -n 3p) + + if [ -z "$RT_PID" ] || [ -z "$FIFO_PID" ] || [ -z "$NORMAL_PID" ]; then + tst_brk $TFAIL "无法获取3个stress-ng R状态进程" + fi + + chrt -r -p 80 $RT_PID + chrt -f -p 80 $FIFO_PID + sleep 1 +} + +cleanup() +{ + killall stress-ng 2>/dev/null || true + if [ -n "$ORIG_RT_RUNTIME" ]; then + sysctl -w kernel.sched_rt_runtime_us=$ORIG_RT_RUNTIME > /dev/null 2>&1 + fi + rm -rf $0.log* 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local rt_policy=$(cat /proc/$RT_PID/sched | grep policy | awk '{print $3}') + local fifo_policy=$(cat /proc/$FIFO_PID/sched | grep policy | awk '{print $3}') + local normal_policy=$(cat /proc/$NORMAL_PID/sched | grep policy | awk '{print $3}') + local fail=0 + [ "$rt_policy" -ne 2 ] && { tst_res $TFAIL "rr policy expected 2, got $rt_policy"; fail=1; } + [ "$fifo_policy" -ne 1 ] && { tst_res $TFAIL "fifo policy expected 1, got $fifo_policy"; fail=1; } + [ "$normal_policy" -ne 0 ] && { tst_res $TFAIL "normal policy expected 0, got $normal_policy"; fail=1; } + [ $fail -eq 0 ] && tst_res $TPASS "RT/FIFO/NORMAL process policies set correctly" + ;; + 1) + local rt_exec=$(cat /proc/$RT_PID/sched | grep sum_exec_runtime | awk '{print $3}' | awk -F '.' '{print $1}') + local fifo_exec=$(cat /proc/$FIFO_PID/sched | grep sum_exec_runtime | awk '{print $3}' | awk -F '.' '{print $1}') + local normal_exec=$(cat /proc/$NORMAL_PID/sched | grep sum_exec_runtime | awk '{print $3}' | awk -F '.' '{print $1}') + sleep 2 + local rt_exec_end=$(cat /proc/$RT_PID/sched | grep sum_exec_runtime | awk '{print $3}' | awk -F '.' '{print $1}') + local fifo_exec_end=$(cat /proc/$FIFO_PID/sched | grep sum_exec_runtime | awk '{print $3}' | awk -F '.' '{print $1}') + local normal_exec_end=$(cat /proc/$NORMAL_PID/sched | grep sum_exec_runtime | awk '{print $3}' | awk -F '.' '{print $1}') + + local fail=0 + [ "$rt_exec_end" -le "$rt_exec" ] && { tst_res $TFAIL "rt process not scheduled"; fail=1; } + [ "$fifo_exec_end" -le "$fifo_exec" ] && { tst_res $TFAIL "fifo process not scheduled"; fail=1; } + [ "$normal_exec_end" -le "$normal_exec" ] && { tst_res $TFAIL "normal process not scheduled"; fail=1; } + [ $fail -eq 0 ] && tst_res $TPASS "all three process types are being scheduled" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/sched/task_status_check_tc001.sh b/os_smoke_src/testcases/kernel/sched/task_status_check_tc001.sh new file mode 100755 index 0000000..8e50fb8 --- /dev/null +++ b/os_smoke_src/testcases/kernel/sched/task_status_check_tc001.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: task_status_check_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 支持进程运行状态检查 +# 前置条件: yum install -y util-linux stress-ng procps-ng +# 步骤: +# 1. 启动stress-ng,获取S状态和R状态进程 +# 2. 发送SIGSTOP信号,验证进程状态变为T +# 3. 发送SIGCONT信号,验证进程状态恢复为R +# 预期结果: +# 1. 获取到S和R状态进程 +# 2. 进程状态变为T +# 3. 进程状态恢复为R +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=( + "获取S/R状态进程" + "SIGSTOP使进程变为T状态" + "SIGCONT使进程恢复R状态" +) +tcnt=${#TCASE_DESC[@]} + +S_PID="" +R_PID="" + +setup() +{ + killall stress-ng 2>/dev/null || true + stress-ng -c 1 -l 100 > /dev/null 2>&1 & + sleep 3 + S_PID=$(ps -eo pid,stat,comm | grep stress-ng | awk '$2~/^S/ {print $1; exit}') + R_PID=$(ps -eo pid,stat,comm | grep stress-ng | awk '$2~/^R/ {print $1; exit}') +} + +cleanup() +{ + [ -n "$R_PID" ] && kill -SIGCONT $R_PID 2>/dev/null || true + killall stress-ng 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + case $n in + 0) + local status_s=$(cat /proc/$S_PID/stat 2>/dev/null | awk '{print $3}') + local status_r=$(cat /proc/$R_PID/stat 2>/dev/null | awk '{print $3}') + local fail=0 + [ "$status_s" != "S" ] && { tst_res $TFAIL "expected S status, got $status_s"; fail=1; } + [ "$status_r" != "R" ] && { tst_res $TFAIL "expected R status, got $status_r"; fail=1; } + [ $fail -eq 0 ] && tst_res $TPASS "S and R status processes found" + ;; + 1) + # 重新获取R状态进程,防止setup时的pid已失效 + R_PID=$(ps -eo pid,stat,comm | grep stress-ng | awk '$2~/^R/ {print $1; exit}') + if [ -z "$R_PID" ]; then + tst_brk $TBROK "No R-status stress-ng process found" + fi + kill -SIGSTOP $R_PID + sleep 2 + local status=$(cat /proc/$R_PID/stat 2>/dev/null | awk '{print $3}') + if [ "$status" = "T" ]; then + tst_res $TPASS "SIGSTOP: process status changed to T" + else + tst_res $TFAIL "SIGSTOP: expected T status, got $status" + fi + ;; + 2) + kill -SIGCONT $R_PID + sleep 3 + local status=$(cat /proc/$R_PID/stat 2>/dev/null | awk '{print $3}') + if [ "$status" = "R" ]; then + tst_res $TPASS "SIGCONT: process status restored to R" + else + tst_res $TFAIL "SIGCONT: expected R status, got $status" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/storage/Makefile b/os_smoke_src/testcases/kernel/storage/Makefile new file mode 100755 index 0000000..f141d30 --- /dev/null +++ b/os_smoke_src/testcases/kernel/storage/Makefile @@ -0,0 +1,36 @@ +CASES_SOURCE := $(wildcard *.c) +CASES_SH := $(wildcard *.sh) +CASES_PY := $(wildcard *.py) +CASES_OBJ := $(patsubst %.c,%.o,$(CASES_SOURCE)) +CASES_BIN := $(patsubst %.o,%,$(CASES_OBJ)) +CFLAGS += -g +LDFLAGS += +INSTALL_DIR := $(subst _src,,$(shell pwd)) + +# 只有存在.c文件时才编译 +ifneq ($(CASES_SOURCE),) +all: $(CASES_BIN) +else +all: + @echo "No .c files found, skipping compilation" +endif + +# 编译规则只在有.c文件时有效 +ifneq ($(CASES_SOURCE),) +$(CASES_BIN):%:%.o + $(CC) $^ -o $@ $(LDFLAGS) + +$(CASES_OBJ):%.o:%.c + $(CC) $(CFLAGS) -c $^ -o $@ +endif + +install: + mkdir -p $(INSTALL_DIR) + cp -avf * $(INSTALL_DIR) + +clean: +ifneq ($(CASES_SOURCE),) + rm -rfv $(CASES_OBJ) $(CASES_BIN) +else + @echo "No .c files found, nothing to clean" +endif diff --git a/os_smoke_src/testcases/kernel/storage/fs_share_target_tc001.sh b/os_smoke_src/testcases/kernel/storage/fs_share_target_tc001.sh new file mode 100755 index 0000000..d6d838d --- /dev/null +++ b/os_smoke_src/testcases/kernel/storage/fs_share_target_tc001.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: fs_share_target_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: NFS文件共享测试(目标端),通过阶段参数执行不同操作 +# 前置条件: 无 +# 步骤: +# 1. 阶段1:安装NFS并启动服务 +# 2. 阶段2:写文件到共享目录 +# 3. 阶段3:读写验证文件 +# 4. 阶段4:还原服务配置 +# 预期结果: +# 1-4. 各阶段操作成功 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=("NFS文件共享目标端操作") +tcnt=${#TCASE_DESC[@]} + +setup() +{ + return 0 +} + +cleanup() +{ + return 0 +} + +run_test() +{ + local n=$1 + local phase="$2" + + case "$phase" in + '1') + systemctl stop firewalld 2>/dev/null || true + install_packages "nfs-utils" + /usr/bin/cp -avf /etc/exports /etc/exports.bak 2>/dev/null + sed -i '/\/tmp\/nfs-share-test/d' /etc/exports + echo '/tmp/nfsserver *(rw,sync,no_root_squash)' >> /etc/exports + mkdir -p /tmp/nfsserver + service nfs-server restart > /dev/null 2>&1 + if service nfs-server status | grep -qi Active; then + tst_res $TPASS "NFS server started" + else + tst_res $TFAIL "NFS server start failed" + fi + ;; + '2') + echo zz > /tmp/zz + cp -avf /tmp/zz /tmp/nfsserver/ > /dev/null 2>&1 + echo xx > /tmp/nfsserver/xx + tst_res $TPASS "target write files completed" + ;; + '3') + local fail=0 + cat /tmp/nfsserver/xx 2>/dev/null | grep -q yy || { tst_res $TFAIL "target read file xx failed"; fail=1; } + cat /tmp/nfsserver/yy 2>/dev/null | grep -q yy || { tst_res $TFAIL "target read file yy failed"; fail=1; } + rm -rf /tmp/yy 2>/dev/null + [ $fail -eq 0 ] && tst_res $TPASS "target read/delete verified" + ;; + '4') + rm -rf /tmp/nfsserver/ 2>/dev/null + /usr/bin/cp -avf /etc/exports.bak /etc/exports 2>/dev/null + service nfs-server restart > /dev/null 2>&1 + tst_res $TPASS "NFS server config restored" + ;; + *) + tst_res $TINFO "no phase specified, doing nothing" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/storage/lvm_adjust_partition_tc001.sh b/os_smoke_src/testcases/kernel/storage/lvm_adjust_partition_tc001.sh new file mode 100755 index 0000000..5c4d2c5 --- /dev/null +++ b/os_smoke_src/testcases/kernel/storage/lvm_adjust_partition_tc001.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: lvm_adjust_partition_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 支持LVM动态调整分区大小,对系统分区容量进行改变 +# 前置条件: 需要空闲磁盘设备 +# 步骤: +# 1. 使用空闲盘创建LVM分区并格式化为ext4 +# 2. 挂载分区,读写文件,验证10G大小 +# 3. 扩容到20G,验证文件和大小 +# 4. 清理LVM资源 +# 预期结果: +# 1. LVM创建成功 +# 2. 10G分区读写正常 +# 3. 扩容到20G后文件依然存在 +# 4. 清理成功 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +source ../../../../control + +TCASE_DESC=( + "LVM创建10G卷并读写验证" + "LVM扩容到20G并验证数据完整性" +) +tcnt=${#TCASE_DESC[@]} + +FREE_DEVICE="" +MOUNT_DIR="/data/lvmtest7701" +LVM_DEVICE="/dev/lvmtest77/lvmtest7701" + +setup() +{ + ensure_command "pvcreate" "lvm2" + if [ -z "${CONTROL_CASE_DISK_DEVICE}" ]; then + tst_brk $TCONF "not config CONTROL_CASE_DISK_DEVICE" + fi + FREE_DEVICE=$(echo $CONTROL_CASE_DISK_DEVICE | awk '{print $1}') +} + +cleanup() +{ + umount $MOUNT_DIR 2>/dev/null || true + lvremove -y $LVM_DEVICE 2>/dev/null || true + vgremove -y lvmtest77 2>/dev/null || true + pvremove -y $FREE_DEVICE 2>/dev/null || true + rm -rf $MOUNT_DIR 2>/dev/null || true +} + +run_test() +{ + local n=$1 + + case $n in + 0) + pvcreate -y $FREE_DEVICE || { tst_res $TFAIL "pvcreate failed"; return; } + vgcreate lvmtest77 $FREE_DEVICE || { tst_res $TFAIL "vgcreate failed"; return; } + lvcreate -y -L 10G -n lvmtest7701 lvmtest77 || { tst_res $TFAIL "lvcreate 10G failed"; return; } + + echo y | mkfs.ext4 $LVM_DEVICE > /dev/null 2>&1 || { tst_res $TFAIL "mkfs.ext4 lvm failed"; return; } + mkdir -p $MOUNT_DIR + mount $LVM_DEVICE $MOUNT_DIR || { tst_res $TFAIL "mount lvm failed"; return; } + + cd $MOUNT_DIR + echo xx > x || { tst_res $TFAIL "write file fail"; cd -; return; } + cat x | grep -q "xx" || { tst_res $TFAIL "read file fail"; cd -; return; } + cd - + + df -h $MOUNT_DIR | grep -q "/dev/mapper/lvmtest77-lvmtest7701" || { tst_res $TFAIL "df check mount failed"; return; } + lsblk | grep lvmtest77-lvmtest7701 | awk '{print $4}' | grep -qi "10G" || { tst_res $TFAIL "lvm 10G size check failed"; return; } + umount $MOUNT_DIR || { tst_res $TFAIL "umount lvm failed"; return; } + tst_res $TPASS "LVM 10G volume created, formatted, mounted, read/write verified" + ;; + 1) + lvresize -L +10G $LVM_DEVICE || { tst_res $TFAIL "lvresize +10G failed"; return; } + e2fsck -f -y $LVM_DEVICE > /dev/null 2>&1 || { tst_res $TFAIL "e2fsck failed"; return; } + resize2fs $LVM_DEVICE > /dev/null 2>&1 || { tst_res $TFAIL "resize2fs failed"; return; } + mount $LVM_DEVICE $MOUNT_DIR || { tst_res $TFAIL "mount after resize failed"; return; } + + cd $MOUNT_DIR + cat x | grep -q "xx" || { tst_res $TFAIL "read file fail after resize"; cd -; return; } + cd - + + df -h $MOUNT_DIR | grep -q "/dev/mapper/lvmtest77-lvmtest7701" || { tst_res $TFAIL "df check mount 2 failed"; return; } + lsblk | grep lvmtest77-lvmtest7701 | awk '{print $4}' | grep -qi "20G" || { tst_res $TFAIL "lvm 20G size check failed"; return; } + + umount $MOUNT_DIR || { tst_res $TFAIL "umount after resize failed"; return; } + lvremove -y $LVM_DEVICE || { tst_res $TFAIL "lvremove failed"; return; } + vgremove -y lvmtest77 || { tst_res $TFAIL "vgremove failed"; return; } + pvremove -y $FREE_DEVICE || { tst_res $TFAIL "pvremove failed"; return; } + tst_res $TPASS "LVM resize to 20G verified, data integrity confirmed, cleanup done" + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/storage/nvme_swap_tc001.sh b/os_smoke_src/testcases/kernel/storage/nvme_swap_tc001.sh new file mode 100755 index 0000000..f2e9ecb --- /dev/null +++ b/os_smoke_src/testcases/kernel/storage/nvme_swap_tc001.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: nvme_swap_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 使用NVMe设备实现内存扩展,与主内存共同工作 +# 前置条件: 需配置CONTROL_CASE_DISK_DEVICE包含NVMe设备 +# 步骤: +# 1. 获取当前swap大小,找到可用NVMe设备 +# 2. mkswap创建swap,swapon启用 +# 3. 验证swap增大 +# 4. swapoff禁用,验证swap还原 +# 预期结果: +# 1. 找到NVMe设备 +# 2. swap启用成功 +# 3. swap大小增加 +# 4. swap还原到原始大小 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../../control + +TCASE_DESC=( + "NVMe swap启用验证" + "NVMe swap禁用还原验证" +) +tcnt=${#TCASE_DESC[@]} + +FREE_NVME="" +ORI_SWAP=0 +SWAPADD_SIZE=0 + +setup() +{ + if [ -z "${CONTROL_CASE_DISK_DEVICE}" ]; then + tst_brk $TCONF "need NVMe device in CONTROL_CASE_DISK_DEVICE" + fi + for disk_name in ${CONTROL_CASE_DISK_DEVICE}; do + echo "$disk_name" | grep -q nvme && { FREE_NVME=$disk_name; break; } + done + if [ -z "$FREE_NVME" ]; then + tst_brk $TCONF "no NVMe device found in CONTROL_CASE_DISK_DEVICE" + fi + ORI_SWAP=$(cat /proc/meminfo | grep SwapTotal | awk '{print $2}') +} + +cleanup() +{ + [ -n "$FREE_NVME" ] && swapoff $FREE_NVME 2>/dev/null || true + rm -rf $0.log 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + mkswap $FREE_NVME > $0.log 2>&1 || { tst_res $TFAIL "mkswap failed"; return; } + SWAPADD_SIZE=1 + if cat $0.log | grep "size =" | grep -q "KiB"; then + SWAPADD_SIZE=$(cat $0.log | grep "size =" | awk -F 'size' '{print $2}' | awk '{print $2}') + elif cat $0.log | grep "size =" | grep -q "bytes"; then + SWAPADD_SIZE=$(cat $0.log | grep "size =" | awk -F 'bytes' '{print $1}' | awk -F '(' '{print $2}' | awk '{print $1}') + SWAPADD_SIZE=$((SWAPADD_SIZE / 1024)) + fi + swapon $FREE_NVME || { tst_res $TFAIL "swapon failed"; return; } + sleep 1 + local swap_size=$(cat /proc/meminfo | grep SwapTotal | awk '{print $2}') + if [ "$swap_size" -ge $((ORI_SWAP + SWAPADD_SIZE - 1024)) ]; then + tst_res $TPASS "NVMe swap enabled, size increased" + else + tst_res $TFAIL "swap size not increased as expected" + fi + ;; + 1) + swapoff $FREE_NVME || { tst_res $TFAIL "swapoff failed"; return; } + sleep 1 + local swap_size=$(cat /proc/meminfo | grep SwapTotal | awk '{print $2}') + if [ "$swap_size" -eq "$ORI_SWAP" ]; then + tst_res $TPASS "NVMe swap disabled, size restored to original" + else + tst_res $TFAIL "swap not restored: expected=$ORI_SWAP actual=$swap_size" + fi + ;; + esac +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/storage/slow_device_caches_tc001.sh b/os_smoke_src/testcases/kernel/storage/slow_device_caches_tc001.sh new file mode 100755 index 0000000..b643e2b --- /dev/null +++ b/os_smoke_src/testcases/kernel/storage/slow_device_caches_tc001.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: slow_device_caches_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 快速块设备作为慢速块设备缓存以加速I/O +# 前置条件: 需配置CONTROL_CASE_DISK_DEVICE含NVMe和SSD/HDD +# 步骤: +# 1. 创建PV/VG/LV,配置cache-pool和writeback模式 +# 2. 格式化ext4,挂载读写验证 +# 预期结果: +# 1. LVM cache配置成功 +# 2. 读写验证通过 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh +source ../../../../control + +TCASE_DESC=("LVM缓存池创建与读写验证") +tcnt=${#TCASE_DESC[@]} + +FREE_NVME="" FREE_SSD="" MOUNT_DIR="/data/cachepool" + +setup() +{ + ensure_command "pvcreate" "lvm2" + if [ -z "${CONTROL_CASE_DISK_DEVICE}" ]; then + tst_brk $TCONF "need NVMe+SSD/HDD in CONTROL_CASE_DISK_DEVICE" + fi + for disk_name in ${CONTROL_CASE_DISK_DEVICE}; do + if echo "$disk_name" | grep -q nvme; then + [ -z "$FREE_NVME" ] && FREE_NVME=$disk_name + else + [ -z "$FREE_SSD" ] && FREE_SSD=$disk_name + fi + done + if [ -z "$FREE_NVME" ] || [ -z "$FREE_SSD" ]; then + tst_brk $TCONF "need both NVMe and SSD/HDD devices" + fi +} + +cleanup() +{ + umount $MOUNT_DIR 2>/dev/null || true + lvremove -f cachepool 2>/dev/null || true + vgremove -f cachepool 2>/dev/null || true + pvremove $FREE_NVME $FREE_SSD 2>/dev/null || true + rm -rf $MOUNT_DIR 2>/dev/null +} + +run_test() +{ + local n=$1 + + pvcreate $FREE_NVME > /dev/null 2>&1 || { tst_res $TFAIL "pvcreate nvme failed"; return; } + pvcreate $FREE_SSD > /dev/null 2>&1 || { tst_res $TFAIL "pvcreate ssd failed"; return; } + vgcreate cachepool $FREE_NVME $FREE_SSD > /dev/null 2>&1 || { tst_res $TFAIL "vgcreate failed"; return; } + + lvcreate -l 100%FREE -n ssd_data cachepool $FREE_SSD > /dev/null 2>&1 + lvcreate -L 100M -n nvme_cache cachepool ${FREE_NVME} > /dev/null 2>&1 + lvcreate -l 70%FREE -n nvme_data cachepool ${FREE_NVME} > /dev/null 2>&1 + + local fail=0 + lvs | grep cachepool | grep -q ssd_data || { tst_res $TFAIL "ssd_data LV not found"; fail=1; } + lvs | grep cachepool | grep -q nvme_cache || { tst_res $TFAIL "nvme_cache LV not found"; fail=1; } + lvs | grep cachepool | grep -q nvme_data || { tst_res $TFAIL "nvme_data LV not found"; fail=1; } + [ $fail -ne 0 ] && return + + lvconvert --type cache-pool --poolmetadata cachepool/nvme_data cachepool/nvme_cache 2>/dev/null + lvconvert --type cache --cachemode writeback --cachepool cachepool/nvme_cache cachepool/ssd_data 2>/dev/null + + mkdir -p $MOUNT_DIR + echo y | mkfs.ext4 /dev/cachepool/ssd_data > /dev/null 2>&1 || { tst_res $TFAIL "mkfs.ext4 failed"; return; } + mount /dev/cachepool/ssd_data $MOUNT_DIR || { tst_res $TFAIL "mount failed"; return; } + + cd $MOUNT_DIR || { tst_res $TFAIL "cd failed"; return; } + echo xx > x || { tst_res $TFAIL "write failed"; cd -; return; } + cat x | grep -q "xx" || { tst_res $TFAIL "read failed"; cd -; return; } + rm -rf x + cd - + + umount $MOUNT_DIR || { tst_res $TFAIL "umount failed"; return; } + tst_res $TPASS "LVM cache pool with writeback mode verified" +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/storage/soft_raid0_tc001.sh b/os_smoke_src/testcases/kernel/storage/soft_raid0_tc001.sh new file mode 100755 index 0000000..8a56172 --- /dev/null +++ b/os_smoke_src/testcases/kernel/storage/soft_raid0_tc001.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: soft_raid0_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 基于loop设备创建软件RAID0阵列,验证ext4读写功能 +# 前置条件: 支持loop设备 +# 步骤: +# 1. 创建2个loop设备,建立RAID0阵列 +# 2. 格式化ext4,挂载读写验证 +# 3. 卸载并停止阵列,清理资源 +# 预期结果: +# 1. RAID0阵列创建成功 +# 2. 读写验证通过 +# 3. 清理成功 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=("RAID0阵列创建与读写验证") +tcnt=${#TCASE_DESC[@]} + +DISK1="" DISK2="" MOUNT_DIR="/data/raid" + +setup() +{ + ensure_command "/usr/sbin/mdadm" "mdadm" + umount $MOUNT_DIR 2>/dev/null; mdadm --stop /dev/md0 2>/dev/null; losetup -D 2>/dev/null +} + +cleanup() +{ + umount $MOUNT_DIR 2>/dev/null || true + mdadm --stop /dev/md0 2>/dev/null || true + [ -n "$DISK1" ] && losetup -d $DISK1 2>/dev/null + [ -n "$DISK2" ] && losetup -d $DISK2 2>/dev/null + rm -rf loop1.img loop2.img $MOUNT_DIR 2>/dev/null +} + +run_test() +{ + local n=$1 + DISK1=$(losetup -f) + dd if=/dev/zero of=loop1.img bs=10M count=10 2>/dev/null + losetup -f loop1.img || { tst_res $TFAIL "losetup loop1 failed"; return; } + DISK2=$(losetup -f) + dd if=/dev/zero of=loop2.img bs=10M count=10 2>/dev/null + losetup -f loop2.img || { tst_res $TFAIL "losetup loop2 failed"; return; } + + test -d $MOUNT_DIR && rm -rf ${MOUNT_DIR}/* || mkdir -p $MOUNT_DIR + echo y | mdadm --create /dev/md0 --level=0 --raid-devices=2 $DISK1 $DISK2 > /dev/null 2>&1 + if [ $? -ne 0 ]; then tst_res $TFAIL "mdadm create RAID0 failed"; return; fi + + mdadm --detail /dev/md0 | grep -q $DISK1 || { tst_res $TFAIL "$DISK1 bind failed"; return; } + mdadm --detail /dev/md0 | grep -q $DISK2 || { tst_res $TFAIL "$DISK2 bind failed"; return; } + lsblk | grep md0 | grep -q raid0 || { tst_res $TFAIL "md0 not raid0 type"; return; } + + echo y | mkfs.ext4 /dev/md0 > /dev/null 2>&1 || { tst_res $TFAIL "mkfs.ext4 failed"; return; } + mount /dev/md0 $MOUNT_DIR || { tst_res $TFAIL "mount failed"; return; } + echo "test raid0" > $MOUNT_DIR/test + if grep -q "test raid0" $MOUNT_DIR/test; then + tst_res $TPASS "RAID0 create, format, mount and read/write verified" + else + tst_res $TFAIL "RAID0 read/write verification failed" + fi + umount /dev/md0; mdadm --stop /dev/md0 2>/dev/null +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/storage/soft_raid10_tc001.sh b/os_smoke_src/testcases/kernel/storage/soft_raid10_tc001.sh new file mode 100755 index 0000000..e08a77f --- /dev/null +++ b/os_smoke_src/testcases/kernel/storage/soft_raid10_tc001.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: soft_raid10_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: 基于4个loop设备创建软件RAID10条带+镜像阵列,验证ext4读写功能 +# 前置条件: 支持loop设备 +# 步骤: +# 1. 创建4个loop设备,建立RAID10阵列 +# 2. 格式化ext4,挂载读写验证 +# 3. 卸载并停止阵列,清理资源 +# 预期结果: +# 1. RAID10阵列创建成功 +# 2. 读写验证通过 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=("RAID10阵列创建与读写验证") +tcnt=${#TCASE_DESC[@]} + +DISK1="" DISK2="" DISK3="" DISK4="" MOUNT_DIR="/data/raid" + +setup() +{ + ensure_command "/usr/sbin/mdadm" "mdadm" + umount $MOUNT_DIR 2>/dev/null; mdadm --stop /dev/md0 2>/dev/null; losetup -D 2>/dev/null +} + +cleanup() +{ + umount $MOUNT_DIR 2>/dev/null || true + mdadm --stop /dev/md0 2>/dev/null || true + for i in 1 2 3 4; do + eval "local d=\$DISK${i}" + [ -n "$d" ] && losetup -d "$d" 2>/dev/null + done + rm -rf loop1.img loop2.img loop3.img loop4.img $MOUNT_DIR 2>/dev/null +} + +run_test() +{ + local n=$1 + local i + for i in 1 2 3 4; do + eval "DISK${i}=\$(losetup -f)" + dd if=/dev/zero of=loop${i}.img bs=10M count=10 2>/dev/null + losetup -f loop${i}.img || { tst_res $TFAIL "losetup loop${i} failed"; return; } + done + + test -d $MOUNT_DIR && rm -rf ${MOUNT_DIR}/* || mkdir -p $MOUNT_DIR + echo y | mdadm --create /dev/md0 --level=10 --raid-devices=4 $DISK1 $DISK2 $DISK3 $DISK4 > /dev/null 2>&1 + if [ $? -ne 0 ]; then tst_res $TFAIL "mdadm create RAID10 failed"; return; fi + + for i in 1 2 3 4; do + eval "local d=\$DISK${i}" + mdadm --detail /dev/md0 | grep -q "$d" || { tst_res $TFAIL "$d bind failed"; return; } + done + lsblk | grep md0 | grep -q raid10 || { tst_res $TFAIL "md0 not raid10 type"; return; } + + echo y | mkfs.ext4 /dev/md0 > /dev/null 2>&1 || { tst_res $TFAIL "mkfs.ext4 failed"; return; } + mount /dev/md0 $MOUNT_DIR || { tst_res $TFAIL "mount failed"; return; } + echo "test raid10" > $MOUNT_DIR/test + if grep -q "test raid10" $MOUNT_DIR/test; then + tst_res $TPASS "RAID10 create, format, mount and read/write verified" + else + tst_res $TFAIL "RAID10 read/write verification failed" + fi + umount /dev/md0; mdadm --stop /dev/md0 2>/dev/null +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/storage/soft_raid1_tc001.sh b/os_smoke_src/testcases/kernel/storage/soft_raid1_tc001.sh new file mode 100755 index 0000000..6aa4886 --- /dev/null +++ b/os_smoke_src/testcases/kernel/storage/soft_raid1_tc001.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: soft_raid1_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: 基于loop设备创建软件RAID1镜像阵列,验证ext4读写功能 +# 前置条件: 支持loop设备 +# 步骤: +# 1. 创建2个loop设备,建立RAID1阵列 +# 2. 格式化ext4,挂载读写验证 +# 3. 卸载并停止阵列,清理资源 +# 预期结果: +# 1. RAID1阵列创建成功 +# 2. 读写验证通过 +# 3. 清理成功 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=("RAID1镜像阵列创建与读写验证") +tcnt=${#TCASE_DESC[@]} + +DISK1="" DISK2="" MOUNT_DIR="/data/raid" + +setup() +{ + ensure_command "/usr/sbin/mdadm" "mdadm" + umount $MOUNT_DIR 2>/dev/null; mdadm --stop /dev/md0 2>/dev/null; losetup -D 2>/dev/null +} + +cleanup() +{ + umount $MOUNT_DIR 2>/dev/null || true + mdadm --stop /dev/md0 2>/dev/null || true + [ -n "$DISK1" ] && losetup -d $DISK1 2>/dev/null + [ -n "$DISK2" ] && losetup -d $DISK2 2>/dev/null + rm -rf loop1.img loop2.img $MOUNT_DIR 2>/dev/null +} + +run_test() +{ + local n=$1 + DISK1=$(losetup -f) + dd if=/dev/zero of=loop1.img bs=10M count=10 2>/dev/null + losetup -f loop1.img || { tst_res $TFAIL "losetup loop1 failed"; return; } + DISK2=$(losetup -f) + dd if=/dev/zero of=loop2.img bs=10M count=10 2>/dev/null + losetup -f loop2.img || { tst_res $TFAIL "losetup loop2 failed"; return; } + + test -d $MOUNT_DIR && rm -rf ${MOUNT_DIR}/* || mkdir -p $MOUNT_DIR + echo y | mdadm --create /dev/md0 --level=1 --raid-devices=2 $DISK1 $DISK2 > /dev/null 2>&1 + if [ $? -ne 0 ]; then tst_res $TFAIL "mdadm create RAID1 failed"; return; fi + + mdadm --detail /dev/md0 | grep -q $DISK1 || { tst_res $TFAIL "$DISK1 bind failed"; return; } + mdadm --detail /dev/md0 | grep -q $DISK2 || { tst_res $TFAIL "$DISK2 bind failed"; return; } + lsblk | grep md0 | grep -q raid1 || { tst_res $TFAIL "md0 not raid1 type"; return; } + + echo y | mkfs.ext4 /dev/md0 > /dev/null 2>&1 || { tst_res $TFAIL "mkfs.ext4 failed"; return; } + mount /dev/md0 $MOUNT_DIR || { tst_res $TFAIL "mount failed"; return; } + echo "test raid1" > $MOUNT_DIR/test + if grep -q "test raid1" $MOUNT_DIR/test; then + tst_res $TPASS "RAID1 create, format, mount and read/write verified" + else + tst_res $TFAIL "RAID1 read/write verification failed" + fi + umount /dev/md0; mdadm --stop /dev/md0 2>/dev/null +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/storage/soft_raid5_tc001.sh b/os_smoke_src/testcases/kernel/storage/soft_raid5_tc001.sh new file mode 100755 index 0000000..c920307 --- /dev/null +++ b/os_smoke_src/testcases/kernel/storage/soft_raid5_tc001.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: soft_raid5_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: 基于3个loop设备创建软件RAID5阵列,验证ext4读写功能 +# 前置条件: 支持loop设备 +# 步骤: +# 1. 创建3个loop设备,建立RAID5阵列 +# 2. 格式化ext4,挂载读写验证 +# 3. 卸载并停止阵列,清理资源 +# 预期结果: +# 1. RAID5阵列创建成功 +# 2. 读写验证通过 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=("RAID5阵列创建与读写验证") +tcnt=${#TCASE_DESC[@]} + +DISK1="" DISK2="" DISK3="" MOUNT_DIR="/data/raid" + +setup() +{ + ensure_command "/usr/sbin/mdadm" "mdadm" + umount $MOUNT_DIR 2>/dev/null; mdadm --stop /dev/md0 2>/dev/null; losetup -D 2>/dev/null +} + +cleanup() +{ + umount $MOUNT_DIR 2>/dev/null || true + mdadm --stop /dev/md0 2>/dev/null || true + [ -n "$DISK1" ] && losetup -d $DISK1 2>/dev/null + [ -n "$DISK2" ] && losetup -d $DISK2 2>/dev/null + [ -n "$DISK3" ] && losetup -d $DISK3 2>/dev/null + rm -rf loop1.img loop2.img loop3.img $MOUNT_DIR 2>/dev/null +} + +run_test() +{ + local n=$1 + local i + for i in 1 2 3; do + eval "DISK${i}=\$(losetup -f)" + dd if=/dev/zero of=loop${i}.img bs=10M count=10 2>/dev/null + losetup -f loop${i}.img || { tst_res $TFAIL "losetup loop${i} failed"; return; } + done + + test -d $MOUNT_DIR && rm -rf ${MOUNT_DIR}/* || mkdir -p $MOUNT_DIR + echo y | mdadm --create /dev/md0 --level=5 --raid-devices=3 $DISK1 $DISK2 $DISK3 > /dev/null 2>&1 + if [ $? -ne 0 ]; then tst_res $TFAIL "mdadm create RAID5 failed"; return; fi + + for i in 1 2 3; do + eval "local d=\$DISK${i}" + mdadm --detail /dev/md0 | grep -q "$d" || { tst_res $TFAIL "$d bind failed"; return; } + done + lsblk | grep md0 | grep -q raid5 || { tst_res $TFAIL "md0 not raid5 type"; return; } + + echo y | mkfs.ext4 /dev/md0 > /dev/null 2>&1 || { tst_res $TFAIL "mkfs.ext4 failed"; return; } + mount /dev/md0 $MOUNT_DIR || { tst_res $TFAIL "mount failed"; return; } + echo "test raid5" > $MOUNT_DIR/test + if grep -q "test raid5" $MOUNT_DIR/test; then + tst_res $TPASS "RAID5 create, format, mount and read/write verified" + else + tst_res $TFAIL "RAID5 read/write verification failed" + fi + umount /dev/md0; mdadm --stop /dev/md0 2>/dev/null +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/storage/soft_raid6_tc001.sh b/os_smoke_src/testcases/kernel/storage/soft_raid6_tc001.sh new file mode 100755 index 0000000..7b03c83 --- /dev/null +++ b/os_smoke_src/testcases/kernel/storage/soft_raid6_tc001.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: soft_raid6_tc001 +# 用例类型: 功能测试 +# 优先级: P1 +# 用例描述: 基于4个loop设备创建软件RAID6双奇偶校验阵列,验证ext4读写功能 +# 前置条件: 支持loop设备 +# 步骤: +# 1. 创建4个loop设备,建立RAID6阵列 +# 2. 格式化ext4,挂载读写验证 +# 3. 卸载并停止阵列,清理资源 +# 预期结果: +# 1. RAID6阵列创建成功 +# 2. 读写验证通过 +#**************************************************# + +source ../../../../tools/case_lib.sh +source ../../../lib/common_libs.sh + +TCASE_DESC=("RAID6阵列创建与读写验证") +tcnt=${#TCASE_DESC[@]} + +DISK1="" DISK2="" DISK3="" DISK4="" MOUNT_DIR="/data/raid" + +setup() +{ + ensure_command "/usr/sbin/mdadm" "mdadm" + umount $MOUNT_DIR 2>/dev/null; mdadm --stop /dev/md0 2>/dev/null; losetup -D 2>/dev/null +} + +cleanup() +{ + umount $MOUNT_DIR 2>/dev/null || true + mdadm --stop /dev/md0 2>/dev/null || true + for i in 1 2 3 4; do + eval "local d=\$DISK${i}" + [ -n "$d" ] && losetup -d "$d" 2>/dev/null + done + rm -rf loop1.img loop2.img loop3.img loop4.img $MOUNT_DIR 2>/dev/null +} + +run_test() +{ + local n=$1 + local i + for i in 1 2 3 4; do + eval "DISK${i}=\$(losetup -f)" + dd if=/dev/zero of=loop${i}.img bs=10M count=10 2>/dev/null + losetup -f loop${i}.img || { tst_res $TFAIL "losetup loop${i} failed"; return; } + done + + test -d $MOUNT_DIR && rm -rf ${MOUNT_DIR}/* || mkdir -p $MOUNT_DIR + echo y | mdadm --create /dev/md0 --level=6 --raid-devices=4 $DISK1 $DISK2 $DISK3 $DISK4 > /dev/null 2>&1 + if [ $? -ne 0 ]; then tst_res $TFAIL "mdadm create RAID6 failed"; return; fi + + for i in 1 2 3 4; do + eval "local d=\$DISK${i}" + mdadm --detail /dev/md0 | grep -q "$d" || { tst_res $TFAIL "$d bind failed"; return; } + done + lsblk | grep md0 | grep -q raid6 || { tst_res $TFAIL "md0 not raid6 type"; return; } + + echo y | mkfs.ext4 /dev/md0 > /dev/null 2>&1 || { tst_res $TFAIL "mkfs.ext4 failed"; return; } + mount /dev/md0 $MOUNT_DIR || { tst_res $TFAIL "mount failed"; return; } + echo "test raid6" > $MOUNT_DIR/test + if grep -q "test raid6" $MOUNT_DIR/test; then + tst_res $TPASS "RAID6 create, format, mount and read/write verified" + else + tst_res $TFAIL "RAID6 read/write verification failed" + fi + umount /dev/md0; mdadm --stop /dev/md0 2>/dev/null +} + +test_main "$@" diff --git a/os_smoke_src/testcases/kernel/storage/swap_tc001.sh b/os_smoke_src/testcases/kernel/storage/swap_tc001.sh new file mode 100755 index 0000000..4a53d9c --- /dev/null +++ b/os_smoke_src/testcases/kernel/storage/swap_tc001.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: kevinzcheng +# Date: 2026/03/11 + +#**************************************************# +# 用例名称: swap_tc001 +# 用例类型: 功能测试 +# 优先级: P0 +# 用例描述: 将文件作为虚拟扩展内存 +# 前置条件: 无 +# 步骤: +# 1. 获取当前swap大小,dd创建2G文件 +# 2. mkswap+swapon启用swap +# 3. 验证swap增加约2G +# 4. swapoff禁用,验证还原 +# 预期结果: +# 1-2. swap文件创建启用成功 +# 3. swap增加约2G +# 4. swap还原到原始大小 +#**************************************************# + +source ../../../../tools/case_lib.sh + +TCASE_DESC=( + "swap文件启用验证" + "swap文件禁用还原验证" +) +tcnt=${#TCASE_DESC[@]} + +ORI_SWAP=0 +SWAPADD_SIZE=2048000 + +setup() +{ + ORI_SWAP=$(cat /proc/meminfo | grep SwapTotal | awk '{print $2}') + dd if=/dev/zero of=swapadd bs=1024 count=$SWAPADD_SIZE 2>/dev/null + chmod 600 swapadd + mkswap swapadd > /dev/null 2>&1 || tst_brk $TFAIL "mkswap failed" +} + +cleanup() +{ + swapoff swapadd 2>/dev/null || true + rm -rf swapadd 2>/dev/null +} + +run_test() +{ + local n=$1 + + case $n in + 0) + swapon swapadd || { tst_res $TFAIL "swapon failed"; return; } + sleep 1 + local swap_size=$(cat /proc/meminfo | grep SwapTotal | awk '{print $2}') + if [ "$swap_size" -ge $((ORI_SWAP + SWAPADD_SIZE - 1024)) ]; then + tst_res $TPASS "swap file enabled, size increased by ~2G" + else + tst_res $TFAIL "swap size not increased: expected>=$((ORI_SWAP + SWAPADD_SIZE - 1024)) actual=$swap_size" + fi + ;; + 1) + swapoff swapadd || { tst_res $TFAIL "swapoff failed"; return; } + sleep 1 + local swap_size=$(cat /proc/meminfo | grep SwapTotal | awk '{print $2}') + if [ "$swap_size" -eq "$ORI_SWAP" ]; then + tst_res $TPASS "swap file disabled, size restored" + else + tst_res $TFAIL "swap not restored: expected=$ORI_SWAP actual=$swap_size" + fi + ;; + esac +} + +test_main "$@" diff --git a/post_uninit.sh b/post_uninit.sh new file mode 100755 index 0000000..719e1c8 --- /dev/null +++ b/post_uninit.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +echo "in post_uninit" +#测试套编译后置脚本函数 +post_uninit_compile() +{ + echo "in postuinit_compile" + +} + +#测试套测试后置脚本函数,非用例级,测试套级 +post_uninit_test() +{ + echo "in post_uninit_test" +} diff --git a/pre_init.sh b/pre_init.sh new file mode 100755 index 0000000..b744048 --- /dev/null +++ b/pre_init.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +echo "in pre_init" + +#测试套编译前置脚本函数 +pre_init_compile() +{ + echo "in pre_init_compile" + +} + +#测试套测试前置脚本函数,非用例级,测试套级 +pre_init_test() +{ + echo "in pre_init_test" +} diff --git a/tools/case_lib.c b/tools/case_lib.c new file mode 100644 index 0000000..6bf0e08 --- /dev/null +++ b/tools/case_lib.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2026 Tencent. All Rights Reserved. + * Author: shankslong + * Date: 2026/03/11 + * + * 公共测试框架 - 所有用例共享 + * 使用方式: 在用例文件中 #include "case_lib.c" + */ + +#ifndef _COMMON_C_ +#define _COMMON_C_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * ============================================================================ + * 测试结果类型定义 + * ============================================================================ + */ + +#define TPASS 0 /* 测试通过 */ +#define TFAIL 1 /* 测试失败 */ +#define TCONF 32 /* 测试配置不满足/跳过 */ +#define TWARN 4 /* 警告(非致命) */ +#define TINFO 16 /* 信息输出 */ + +/* + * ============================================================================ + * 全局测试状态 + * ============================================================================ + */ + +static int tst_count = 0; /* 当前测试编号 */ +static int tst_pass = 0; /* 通过计数 */ +static int tst_fail = 0; /* 失败计数 */ +static const char *tst_name = "test"; /* 测试名称 */ + +/* + * ============================================================================ + * 测试结果输出函数 + * ============================================================================ + */ + +/* + * tst_res - 输出测试结果 + * @ttype: 结果类型 (TPASS/TFAIL/TCONF/TWARN/TINFO) + * @fmt: 格式化字符串 + */ +static void tst_res(int ttype, const char *fmt, ...) +{ + const char *type_str; + va_list ap; + + switch (ttype) { + case TPASS: + type_str = "PASS"; + tst_pass++; + break; + case TFAIL: + type_str = "FAIL"; + tst_fail++; + break; + case TCONF: + type_str = "CONF"; + break; + case TWARN: + type_str = "WARN"; + break; + case TINFO: + type_str = "INFO"; + break; + default: + type_str = "????"; + break; + } + + printf("%s %d %s: ", tst_name, ++tst_count, type_str); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + + printf("\n"); +} + +/* + * tst_brk - 致命错误退出 + * @ttype: 错误类型 + * @fmt: 格式化字符串 + */ +static void tst_brk(int ttype, const char *fmt, ...) +{ + va_list ap; + + printf("%s %d BROK: ", tst_name, ++tst_count); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + + printf("\n"); + + exit(ttype == TCONF ? 32 : 1); +} + +/* + * ============================================================================ + * 安全系统调用宏(带错误检查) + * ============================================================================ + */ + +#define SAFE_OPEN(path, flags, ...) \ + ({ \ + int _fd = open(path, flags, ##__VA_ARGS__); \ + if (_fd < 0) \ + tst_brk(TFAIL, "open(%s) failed: %s", path, strerror(errno)); \ + _fd; \ + }) + +#define SAFE_CLOSE(fd) \ + do { \ + if (close(fd) < 0) \ + tst_brk(TFAIL, "close() failed: %s", strerror(errno)); \ + } while (0) + +#define SAFE_READ(fd, buf, count) \ + ({ \ + ssize_t _ret = read(fd, buf, count); \ + if (_ret < 0) \ + tst_brk(TFAIL, "read() failed: %s", strerror(errno)); \ + _ret; \ + }) + +#define SAFE_WRITE(fd, buf, count) \ + ({ \ + ssize_t _ret = write(fd, buf, count); \ + if (_ret < 0) \ + tst_brk(TFAIL, "write() failed: %s", strerror(errno)); \ + _ret; \ + }) + +#define SAFE_MALLOC(size) \ + ({ \ + void *_ptr = malloc(size); \ + if (!_ptr) \ + tst_brk(TFAIL, "malloc(%zu) failed", (size_t)(size)); \ + _ptr; \ + }) + +#define SAFE_FREE(ptr) \ + do { \ + free(ptr); \ + ptr = NULL; \ + } while (0) + +/* + * ============================================================================ + * 通用辅助函数 + * ============================================================================ + */ + +/* + * check_kernel_version - 检查内核版本是否满足要求 + * @min_kver: 最低内核版本字符串 (如 "4.0" 或 "5.10") + * 返回: 1-满足, 0-不满足 + */ +static int check_kernel_version(const char *min_kver) +{ + FILE *fp; + int major, minor, patch; + int req_major, req_minor; + + fp = fopen("/proc/version", "r"); + if (!fp) + return 0; + + if (fscanf(fp, "Linux version %d.%d.%d", &major, &minor, &patch) != 3) { + fclose(fp); + return 0; + } + fclose(fp); + + if (sscanf(min_kver, "%d.%d", &req_major, &req_minor) != 2) + return 0; + + if (major > req_major) + return 1; + if (major == req_major && minor >= req_minor) + return 1; + + return 0; +} + +/* + * check_root - 检查是否具有root权限 + * 返回: 1-是root, 0-不是 + */ +static int __attribute__((unused)) check_root(void) +{ + return (geteuid() == 0); +} + +/* + * file_exists - 检查文件是否存在 + * @path: 文件路径 + * 返回: 1-存在, 0-不存在 + */ +static int __attribute__((unused)) file_exists(const char *path) +{ + return (access(path, F_OK) == 0); +} + +/* + * read_sysfs_int - 从sysfs读取整数值 + * @path: sysfs文件路径 + * @value: 输出值指针 + * 返回: 0-成功, -1-失败 + */ +static int __attribute__((unused)) read_sysfs_int(const char *path, int *value) +{ + FILE *fp = fopen(path, "r"); + if (!fp) + return -1; + + if (fscanf(fp, "%d", value) != 1) { + fclose(fp); + return -1; + } + + fclose(fp); + return 0; +} + +/* + * write_sysfs_int - 向sysfs写入整数值 + * @path: sysfs文件路径 + * @value: 要写入的值 + * 返回: 0-成功, -1-失败 + */ +static int __attribute__((unused)) write_sysfs_int(const char *path, int value) +{ + FILE *fp = fopen(path, "w"); + if (!fp) + return -1; + + if (fprintf(fp, "%d", value) < 0) { + fclose(fp); + return -1; + } + + fclose(fp); + return 0; +} + +/* + * is_virtual_machine - 检查是否为虚拟机环境 + * 返回: 1-虚拟机, 0-物理机 + * + * 检测方法: + * 1. 读取 /sys/class/dmi/id/product_name 检测虚拟化产品名 + * 2. 读取 /proc/cpuinfo 检测 hypervisor 标志 + * 3. 使用 systemd-detect-virt 命令(如果可用) + */ +static int __attribute__((unused)) is_virtual_machine(void) +{ + FILE *fp; + char buf[256]; + + /* 方法1: 检查 DMI product_name */ + fp = fopen("/sys/class/dmi/id/product_name", "r"); + if (fp) { + if (fgets(buf, sizeof(buf), fp)) { + fclose(fp); + /* 常见虚拟机产品名 */ + if (strstr(buf, "Virtual") || /* VMware, Hyper-V */ + strstr(buf, "KVM") || /* KVM/QEMU */ + strstr(buf, "QEMU") || /* QEMU */ + strstr(buf, "VirtualBox") || /* VirtualBox */ + strstr(buf, "Xen") || /* Xen */ + strstr(buf, "Bochs") || /* Bochs */ + strstr(buf, "HVM domU") || /* Xen HVM */ + strstr(buf, "OpenStack") || /* OpenStack */ + strstr(buf, "CVM")) /* 腾讯云 CVM */ + return 1; + } else { + fclose(fp); + } + } + + /* 方法2: 检查 DMI sys_vendor */ + fp = fopen("/sys/class/dmi/id/sys_vendor", "r"); + if (fp) { + if (fgets(buf, sizeof(buf), fp)) { + fclose(fp); + if (strstr(buf, "QEMU") || + strstr(buf, "VMware") || + strstr(buf, "Xen") || + strstr(buf, "Microsoft") || /* Hyper-V */ + strstr(buf, "innotek") || /* VirtualBox */ + strstr(buf, "Parallels") || + strstr(buf, "Tencent Cloud")) + return 1; + } else { + fclose(fp); + } + } + + /* 方法3: 检查 /proc/cpuinfo 的 hypervisor 标志 */ + fp = fopen("/proc/cpuinfo", "r"); + if (fp) { + while (fgets(buf, sizeof(buf), fp)) { + if (strstr(buf, "flags") && strstr(buf, "hypervisor")) { + fclose(fp); + return 1; + } + } + fclose(fp); + } + + /* 方法4: 检查 /sys/hypervisor/type */ + fp = fopen("/sys/hypervisor/type", "r"); + if (fp) { + if (fgets(buf, sizeof(buf), fp)) { + fclose(fp); + if (strlen(buf) > 0) + return 1; + } else { + fclose(fp); + } + } + + return 0; +} + +/* + * ============================================================================ + * 测试框架主函数 + * ============================================================================ + */ + +/* 用例需要实现的函数声明 */ +static void setup(void); +static void cleanup(void); +static void run_test(unsigned int n); + +/* 用例需要定义的变量 */ +extern unsigned int tcnt; /* 测试用例总数,由用例定义 */ + +static void print_usage(const char *prog) +{ + printf("用法: %s [选项]\n", prog); + printf("选项:\n"); + printf(" -h 显示帮助信息\n"); + printf(" -i 运行迭代次数 (默认: 1, 0表示无限循环)\n"); + printf("\n"); +} + +static int test_main(int argc, char *argv[]) +{ + int opt; + int iterations = 1; + unsigned int i, iter; + + /* 设置测试名称(从程序名提取) */ + tst_name = strrchr(argv[0], '/'); + tst_name = tst_name ? tst_name + 1 : argv[0]; + + /* 解析命令行参数 */ + while ((opt = getopt(argc, argv, "hi:")) != -1) { + switch (opt) { + case 'h': + print_usage(argv[0]); + return 0; + case 'i': + iterations = atoi(optarg); + break; + default: + print_usage(argv[0]); + return 1; + } + } + + printf("========================================\n"); + printf("测试名称: %s\n", tst_name); + printf("测试用例数: %u\n", tcnt); + printf("迭代次数: %s\n", iterations == 0 ? "无限" : + (iterations == 1 ? "1" : "多次")); + printf("========================================\n\n"); + + /* 执行初始化 */ + setup(); + + /* 执行测试迭代 */ + for (iter = 0; iterations == 0 || iter < (unsigned int)iterations; iter++) { + if (iterations != 1) { + printf("\n--- 迭代 #%u ---\n", iter + 1); + } + + /* 遍历所有测试用例 */ + for (i = 0; i < tcnt; i++) { + run_test(i); + } + } + + /* 执行清理 */ + cleanup(); + + /* 输出测试汇总 */ + printf("\n========================================\n"); + printf("C 辅助程序 测试汇总:\n"); + printf(" 总计: %d\n", tst_pass + tst_fail); + printf(" 通过: %d\n", tst_pass); + printf(" 失败: %d\n", tst_fail); + printf("========================================\n"); + + /* 返回状态码 */ + return tst_fail > 0 ? 1 : 0; +} + +#endif /* _COMMON_C_ */ diff --git a/tools/case_lib.py b/tools/case_lib.py new file mode 100755 index 0000000..0161e4a --- /dev/null +++ b/tools/case_lib.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: shankslong +# Date: 2026/03/11 +# +# Python测试框架库 - 所有Python用例共享 +# 使用方式: from case_lib import CaseLib +# +# 对标 case_lib.c,提供统一的测试结果输出、断言、 +# 安全命令执行、环境检测等基础能力。 + +import os +import re +import sys +import platform +import argparse +import subprocess +from typing import Any, Callable, Dict, List, Optional, Tuple +from dataclasses import dataclass + + +# ============================================================================= +# 测试结果类型定义(对标 case_lib.c 的 TPASS/TFAIL/TCONF/TWARN/TINFO) +# ============================================================================= + +TPASS = 0 # 测试通过 +TFAIL = 1 # 测试失败 +TCONF = 32 # 测试配置不满足/跳过 +TWARN = 4 # 警告(非致命) +TINFO = 16 # 信息输出 + + +# ============================================================================= +# 测试用例数据结构(对标 case_lib.c / case.c 的 struct tcase) +# ============================================================================= + +@dataclass +class TCase: + """测试用例数据结构 + + 对标 case.c 中的 struct tcase,用于组织单条测试数据。 + 用例通过继承 CaseLib 并填充 tcases 列表来定义测试数据。 + """ + desc: str # 测试描述 + params: Optional[Dict[str, Any]] = None # 测试参数(灵活键值对) + + +# ============================================================================= +# 致命错误异常 +# ============================================================================= + +class TestBrk(SystemExit): + """致命测试错误异常(对标 case_lib.c 的 tst_brk) + + 抛出后终止整个测试流程。 + """ + + def __init__(self, ttype: int, msg: str): + self.ttype = ttype + self.msg = msg + code = 32 if ttype == TCONF else 1 + super().__init__(code) + + +# ============================================================================= +# 测试框架核心类(对标 case_lib.c 的全局状态 + 所有函数) +# ============================================================================= + +class CaseLib: + """Python测试框架核心类 + + 对标 case_lib.c,提供: + - 测试结果输出(tst_res / tst_brk) + - 安全命令执行(safe_cmd / safe_file_read / safe_file_write / safe_mkdir) + - 环境检测(check_kernel_version / check_root / file_exists / + read_sysfs_int / write_sysfs_int / is_virtual_machine) + - 测试主流程(test_main: 解析参数 → setup → 迭代 run_test → cleanup → 汇总) + + 用例继承此类,实现 setup / cleanup / run_test 方法,并填充 tcases 列表。 + """ + + def __init__(self) -> None: + # 全局测试状态(对标 case_lib.c 的全局变量) + self.tst_count: int = 0 + self.tst_pass: int = 0 + self.tst_fail: int = 0 + self.tst_name: str = os.path.basename(sys.argv[0]).replace('.py', '') + + # 测试用例数据列表(由子类填充,对标 case.c 的 tcases[] + tcnt) + self.tcases: List[TCase] = [] + + # ========================================================================= + # 测试结果输出函数(对标 case_lib.c 的 tst_res / tst_brk) + # ========================================================================= + + def tst_res(self, ttype: int, msg: str) -> None: + """输出测试结果 + + 对标 case_lib.c 的 tst_res()。 + 根据 ttype 自动递增 PASS/FAIL 计数器,并格式化输出。 + + Args: + ttype: 结果类型 (TPASS/TFAIL/TCONF/TWARN/TINFO) + msg: 描述信息 + """ + type_map = { + TPASS: "PASS", + TFAIL: "FAIL", + TCONF: "CONF", + TWARN: "WARN", + TINFO: "INFO", + } + type_str = type_map.get(ttype, "????") + + if ttype == TPASS: + self.tst_pass += 1 + elif ttype == TFAIL: + self.tst_fail += 1 + + self.tst_count += 1 + print(f"{self.tst_name} {self.tst_count} {type_str}: {msg}") + + def tst_brk(self, ttype: int, msg: str) -> None: + """致命错误退出 + + 对标 case_lib.c 的 tst_brk()。 + 输出 BROK 信息后抛出 TestBrk 异常终止测试。 + + Args: + ttype: 错误类型 + msg: 错误描述 + """ + self.tst_count += 1 + print(f"{self.tst_name} {self.tst_count} BROK: {msg}") + raise TestBrk(ttype, msg) + + # ========================================================================= + # 安全命令执行(对标 case_lib.c 的 SAFE_* 宏) + # ========================================================================= + + def safe_cmd(self, desc: str, cmd: List[str], **kwargs: Any) -> subprocess.CompletedProcess: + """安全执行命令,失败时调用 tst_brk 退出 + + 对标 case_lib.c 的 SAFE_CMD 系列宏。 + + Args: + desc: 命令描述 + cmd: 命令及参数列表 + **kwargs: 传递给 subprocess.run 的额外参数 + + Returns: + subprocess.CompletedProcess 对象 + """ + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + **kwargs + ) + if result.returncode != 0: + self.tst_brk(TFAIL, f"{desc} failed: {' '.join(cmd)}" + f" (rc={result.returncode}, stderr={result.stderr.strip()})") + return result + except Exception as e: + self.tst_brk(TFAIL, f"{desc} failed: {e}") + + def safe_file_read(self, path: str) -> str: + """安全读取文件内容 + + 对标 case_lib.c 的 SAFE_READ / SAFE_OPEN 组合。 + + Args: + path: 文件路径 + + Returns: + 文件内容字符串 + """ + if not os.path.isfile(path): + self.tst_brk(TFAIL, f"file not found: {path}") + try: + with open(path, 'r') as f: + return f.read() + except Exception as e: + self.tst_brk(TFAIL, f"read {path} failed: {e}") + + def safe_file_write(self, path: str, content: str) -> None: + """安全写入文件 + + 对标 case_lib.c 的 SAFE_WRITE / SAFE_OPEN 组合。 + + Args: + path: 文件路径 + content: 写入内容 + """ + try: + with open(path, 'w') as f: + f.write(content) + except Exception as e: + self.tst_brk(TFAIL, f"write to {path} failed: {e}") + + def safe_mkdir(self, path: str) -> None: + """安全创建目录 + + Args: + path: 目录路径 + """ + try: + os.makedirs(path, exist_ok=True) + except Exception as e: + self.tst_brk(TFAIL, f"mkdir {path} failed: {e}") + + # ========================================================================= + # 通用辅助函数(对标 case_lib.c 的辅助函数) + # ========================================================================= + + @staticmethod + def check_kernel_version(min_kver: str) -> bool: + """检查内核版本是否满足要求 + + 对标 case_lib.c 的 check_kernel_version()。 + + Args: + min_kver: 最低内核版本字符串 (如 "4.0" 或 "5.10") + + Returns: + True-满足, False-不满足 + """ + try: + parts = min_kver.split('.') + req_major = int(parts[0]) + req_minor = int(parts[1]) if len(parts) > 1 else 0 + except (ValueError, IndexError): + return False + + kver = platform.release() + match = re.match(r'^(\d+)\.(\d+)', kver) + if not match: + return False + + cur_major = int(match.group(1)) + cur_minor = int(match.group(2)) + + if cur_major > req_major: + return True + if cur_major == req_major and cur_minor >= req_minor: + return True + return False + + @staticmethod + def check_root() -> bool: + """检查是否具有root权限 + + 对标 case_lib.c 的 check_root()。 + + Returns: + True-是root, False-不是 + """ + return os.geteuid() == 0 + + @staticmethod + def file_exists(path: str) -> bool: + """检查文件是否存在 + + 对标 case_lib.c 的 file_exists()。 + + Args: + path: 文件路径 + + Returns: + True-存在, False-不存在 + """ + return os.path.isfile(path) + + @staticmethod + def read_sysfs_int(path: str) -> Optional[int]: + """从sysfs读取整数值 + + 对标 case_lib.c 的 read_sysfs_int()。 + + Args: + path: sysfs文件路径 + + Returns: + 读取的整数值,失败返回 None + """ + try: + with open(path, 'r') as f: + return int(f.read().strip()) + except (IOError, ValueError): + return None + + @staticmethod + def write_sysfs_int(path: str, value: int) -> bool: + """向sysfs写入整数值 + + 对标 case_lib.c 的 write_sysfs_int()。 + + Args: + path: sysfs文件路径 + value: 要写入的值 + + Returns: + True-成功, False-失败 + """ + try: + with open(path, 'w') as f: + f.write(str(value)) + return True + except (IOError, OSError): + return False + + @staticmethod + def is_virtual_machine() -> bool: + """检查是否为虚拟机环境 + + 对标 case_lib.c 的 is_virtual_machine()。 + 检测方法: + 1. 读取 /sys/class/dmi/id/product_name 检测虚拟化产品名 + 2. 读取 /sys/class/dmi/id/sys_vendor 检测厂商 + 3. 检查 /proc/cpuinfo 的 hypervisor 标志 + 4. 检查 /sys/hypervisor/type + + Returns: + True-虚拟机, False-物理机 + """ + # 方法1: 检查 DMI product_name + vm_product_keywords = [ + "Virtual", "KVM", "QEMU", "VirtualBox", "Xen", + "Bochs", "HVM domU", "OpenStack", "CVM" + ] + try: + with open("/sys/class/dmi/id/product_name", "r") as f: + buf = f.read().strip() + for kw in vm_product_keywords: + if kw in buf: + return True + except (IOError, OSError): + pass + + # 方法2: 检查 DMI sys_vendor + vm_vendor_keywords = [ + "QEMU", "VMware", "Xen", "Microsoft", + "innotek", "Parallels", "Tencent Cloud" + ] + try: + with open("/sys/class/dmi/id/sys_vendor", "r") as f: + buf = f.read().strip() + for kw in vm_vendor_keywords: + if kw in buf: + return True + except (IOError, OSError): + pass + + # 方法3: 检查 /proc/cpuinfo 的 hypervisor 标志 + try: + with open("/proc/cpuinfo", "r") as f: + for line in f: + if "flags" in line and "hypervisor" in line: + return True + except (IOError, OSError): + pass + + # 方法4: 检查 /sys/hypervisor/type + try: + with open("/sys/hypervisor/type", "r") as f: + buf = f.read().strip() + if buf: + return True + except (IOError, OSError): + pass + + return False + + # ========================================================================= + # 用例需要实现的方法(对标 case_lib.c 声明的 setup/cleanup/run_test) + # ========================================================================= + + def setup(self) -> None: + """测试初始化(子类重写) + + 在所有测试用例执行前调用一次。 + """ + pass + + def cleanup(self) -> None: + """测试清理(子类重写) + + 在所有测试用例执行后调用一次。 + """ + pass + + def run_test(self, n: int) -> None: + """执行单个测试用例(子类必须重写) + + Args: + n: 测试用例索引(从0开始) + """ + self.tst_brk(TFAIL, "子类必须实现 run_test() 方法") + + # ========================================================================= + # 测试框架主函数(对标 case_lib.c 的 test_main) + # ========================================================================= + + def test_main(self) -> None: + """测试主入口 + + 对标 case_lib.c 的 test_main()。 + 流程: 解析参数 → 打印测试信息 → setup → 迭代 run_test → cleanup → 汇总 + """ + # 解析命令行参数 + parser = argparse.ArgumentParser( + description=f"测试: {self.tst_name}", + add_help=False + ) + parser.add_argument('-h', '--help', action='store_true', + default=False, help='显示帮助信息') + parser.add_argument('-i', '--iterations', type=int, default=1, + help='运行迭代次数 (默认: 1, 0表示无限循环)') + args = parser.parse_args() + + if args.help: + print(f"用法: {self.tst_name} [选项]") + print("选项:") + print(" -h 显示帮助信息") + print(" -i 运行迭代次数 (默认: 1, 0表示无限循环)") + print() + sys.exit(0) + + iterations = args.iterations + tcnt = len(self.tcases) + + # 检查用例是否定义了测试数据 + if tcnt <= 0: + self.tst_brk(TFAIL, "用例未定义 tcases 或 tcases 为空") + + if iterations == 0: + iter_str = "无限" + elif iterations == 1: + iter_str = "1" + else: + iter_str = f"多次({iterations})" + + print("========================================") + print(f"测试名称: {self.tst_name}") + print(f"测试用例数: {tcnt}") + print(f"迭代次数: {iter_str}") + print("========================================") + print() + + # 执行初始化 + self.setup() + + # 执行测试迭代 + iter_count = 0 + while iterations == 0 or iter_count < iterations: + if iterations != 1: + print(f"\n--- 迭代 #{iter_count + 1} ---") + + # 遍历所有测试用例 + for i in range(tcnt): + self.run_test(i) + + iter_count += 1 + + # 执行清理 + self.cleanup() + + # 输出测试汇总 + print() + print("========================================") + print("测试汇总:") + print(f" 总计: {self.tst_pass + self.tst_fail}") + print(f" 通过: {self.tst_pass}") + print(f" 失败: {self.tst_fail}") + print("========================================") + + # 返回状态码 + sys.exit(1 if self.tst_fail > 0 else 0) diff --git a/tools/case_lib.sh b/tools/case_lib.sh new file mode 100755 index 0000000..79bd9fe --- /dev/null +++ b/tools/case_lib.sh @@ -0,0 +1,439 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: shankslong +# Date: 2026/03/11 +# +# Shell测试框架库 - 所有shell用例共享 +# 使用方式: 在用例文件中 source "../../../tools/case_lib.sh" +# +# 对标 case_lib.c,提供统一的测试结果输出、断言、 +# 安全命令执行、环境检测等基础能力。 + +# 防止重复source +if [ -n "$_CASE_LIB_SH_LOADED" ]; then + return 0 2>/dev/null || true +fi +_CASE_LIB_SH_LOADED=1 + +# ============================================================================= +# 测试结果类型定义(对标 case_lib.c 的 TPASS/TFAIL/TCONF/TWARN/TINFO) +# ============================================================================= + +readonly TPASS=0 # 测试通过 +readonly TFAIL=1 # 测试失败 +readonly TCONF=32 # 测试配置不满足/跳过 +readonly TWARN=4 # 警告(非致命) +readonly TINFO=16 # 信息输出 + +# ============================================================================= +# 全局测试状态(对标 case_lib.c 的全局变量) +# ============================================================================= + +tst_count=0 # 当前测试编号 +tst_pass=0 # 通过计数 +tst_fail=0 # 失败计数 +tst_name="${0##*/}" # 测试名称(从脚本名提取) +tst_name="${tst_name%.sh}" + +# ============================================================================= +# 测试结果输出函数(对标 case_lib.c 的 tst_res / tst_brk) +# ============================================================================= + +# tst_res - 输出测试结果 +# 用法: tst_res <类型> "描述信息" +# @ttype: 结果类型 (TPASS/TFAIL/TCONF/TWARN/TINFO) +# @msg: 描述信息 +tst_res() +{ + local ttype=$1 + shift + local msg="$*" + local type_str + + case $ttype in + $TPASS) + type_str="PASS" + tst_pass=$((tst_pass + 1)) + ;; + $TFAIL) + type_str="FAIL" + tst_fail=$((tst_fail + 1)) + ;; + $TCONF) + type_str="CONF" + ;; + $TWARN) + type_str="WARN" + ;; + $TINFO) + type_str="INFO" + ;; + *) + type_str="????" + ;; + esac + + tst_count=$((tst_count + 1)) + echo "${tst_name} ${tst_count} ${type_str}: ${msg}" +} + +# tst_brk - 致命错误退出 +# 用法: tst_brk <类型> "错误描述" +# @ttype: 错误类型 +# @msg: 错误描述 +tst_brk() +{ + local ttype=$1 + shift + local msg="$*" + + tst_count=$((tst_count + 1)) + echo "${tst_name} ${tst_count} BROK: ${msg}" + + if [ "$ttype" -eq "$TCONF" ]; then + exit 32 + else + exit 1 + fi +} + +# ============================================================================= +# 安全命令执行宏(对标 case_lib.c 的 SAFE_* 宏) +# ============================================================================= + +# SAFE_CMD - 安全执行命令,失败时调用tst_brk退出 +# 用法: SAFE_CMD "命令描述" 命令 参数... +SAFE_CMD() +{ + local desc="$1" + shift + + "$@" 2>/dev/null + if [ $? -ne 0 ]; then + tst_brk $TFAIL "${desc} failed: $*" + fi +} + +# SAFE_FILE_READ - 安全读取文件内容 +# 用法: SAFE_FILE_READ <文件路径> +# 输出: 文件内容到stdout +SAFE_FILE_READ() +{ + local path="$1" + + if [ ! -f "$path" ]; then + tst_brk $TFAIL "file not found: ${path}" + fi + + cat "$path" 2>/dev/null + if [ $? -ne 0 ]; then + tst_brk $TFAIL "read ${path} failed" + fi +} + +# SAFE_FILE_WRITE - 安全写入文件 +# 用法: SAFE_FILE_WRITE <文件路径> <内容> +SAFE_FILE_WRITE() +{ + local path="$1" + local content="$2" + + echo "$content" > "$path" 2>/dev/null + if [ $? -ne 0 ]; then + tst_brk $TFAIL "write to ${path} failed" + fi +} + +# SAFE_MKDIR - 安全创建目录 +# 用法: SAFE_MKDIR <目录路径> +SAFE_MKDIR() +{ + local path="$1" + + mkdir -p "$path" 2>/dev/null + if [ $? -ne 0 ]; then + tst_brk $TFAIL "mkdir ${path} failed" + fi +} + +# ============================================================================= +# 通用辅助函数(对标 case_lib.c 的辅助函数) +# ============================================================================= + +# check_kernel_version - 检查内核版本是否满足要求 +# @min_kver: 最低内核版本字符串 (如 "4.0" 或 "5.10") +# 返回: 0-满足, 1-不满足 +check_kernel_version() +{ + local min_kver="$1" + local req_major req_minor + local cur_major cur_minor + + req_major=$(echo "$min_kver" | cut -d'.' -f1) + req_minor=$(echo "$min_kver" | cut -d'.' -f2) + + local kver + kver=$(uname -r) + cur_major=$(echo "$kver" | cut -d'.' -f1) + cur_minor=$(echo "$kver" | cut -d'.' -f2) + + if [ -z "$req_major" ] || [ -z "$req_minor" ]; then + return 1 + fi + + if [ "$cur_major" -gt "$req_major" ]; then + return 0 + fi + if [ "$cur_major" -eq "$req_major" ] && [ "$cur_minor" -ge "$req_minor" ]; then + return 0 + fi + + return 1 +} + +# check_root - 检查是否具有root权限 +# 返回: 0-是root, 1-不是 +check_root() +{ + [ "$(id -u)" -eq 0 ] +} + +# file_exists - 检查文件是否存在 +# @path: 文件路径 +# 返回: 0-存在, 1-不存在 +file_exists() +{ + [ -f "$1" ] +} + +# read_sysfs_int - 从sysfs读取整数值 +# @path: sysfs文件路径 +# 输出: OSTEST_ECHO_MSG:<整数值> +# 返回: 0-成功, 1-失败 +read_sysfs_int() +{ + local path="$1" + local value + + if [ ! -f "$path" ]; then + return 1 + fi + + value=$(cat "$path" 2>/dev/null) + if [ $? -ne 0 ] || [ -z "$value" ]; then + return 1 + fi + + echo "OSTEST_ECHO_MSG:${value}" + return 0 +} + +# write_sysfs_int - 向sysfs写入整数值 +# @path: sysfs文件路径 +# @value: 要写入的值 +# 返回: 0-成功, 1-失败 +write_sysfs_int() +{ + local path="$1" + local value="$2" + + if [ ! -f "$path" ]; then + return 1 + fi + + echo "$value" > "$path" 2>/dev/null + return $? +} + +# is_virtual_machine - 检查是否为虚拟机环境 +# 返回: 0-虚拟机, 1-物理机 +# +# 检测方法: +# 1. 读取 /sys/class/dmi/id/product_name 检测虚拟化产品名 +# 2. 读取 /sys/class/dmi/id/sys_vendor 检测厂商 +# 3. 检查 /proc/cpuinfo 的 hypervisor 标志 +# 4. 检查 /sys/hypervisor/type +is_virtual_machine() +{ + local buf + + # 方法1: 检查 DMI product_name + if [ -f "/sys/class/dmi/id/product_name" ]; then + buf=$(cat /sys/class/dmi/id/product_name 2>/dev/null) + case "$buf" in + *Virtual*|*KVM*|*QEMU*|*VirtualBox*|*Xen*|*Bochs*|*"HVM domU"*|*OpenStack*|*CVM*) + return 0 + ;; + esac + fi + + # 方法2: 检查 DMI sys_vendor + if [ -f "/sys/class/dmi/id/sys_vendor" ]; then + buf=$(cat /sys/class/dmi/id/sys_vendor 2>/dev/null) + case "$buf" in + *QEMU*|*VMware*|*Xen*|*Microsoft*|*innotek*|*Parallels*|*"Tencent Cloud"*) + return 0 + ;; + esac + fi + + # 方法3: 检查 /proc/cpuinfo 的 hypervisor 标志 + if grep -q "flags.*hypervisor" /proc/cpuinfo 2>/dev/null; then + return 0 + fi + + # 方法4: 检查 /sys/hypervisor/type + if [ -f "/sys/hypervisor/type" ]; then + buf=$(cat /sys/hypervisor/type 2>/dev/null) + if [ -n "$buf" ]; then + return 0 + fi + fi + + return 1 +} + +# ============================================================================= +# 系统日志清理与检查 +# ============================================================================= + +# clear_dmesg - 清理内核日志缓冲区(测试前置调用) +# 清除 dmesg 中的历史信息,确保测试期间只捕获本次产生的内核日志 +clear_dmesg() +{ + dmesg -c > /dev/null 2>&1 +} + +# check_dmesg - 检查内核日志中是否存在异常信息(测试后置调用) +# 检测关键字: oops, warning, bug, call trace, softlockup, hungtask, out of memory +# 排除关键字: debug, if necessary, Subscription +# 说明: 直接通过管道流式处理dmesg输出,避免大日志存入变量导致内存问题 +# 返回: 0-无异常, 1-发现异常 +check_dmesg() +{ + dmesg | grep -iE "oops|warning| bug |call trace|softlockup|hungtask|out of memory" \ + | grep -Ev "debug|if necessary|Subscription" > /dev/null 2>&1 + + if [ $? -eq 0 ]; then + tst_res $TFAIL "dmesg check: 发现异常内核日志" + echo "--- dmesg异常信息 ---" + dmesg | grep -iE "oops|warning| bug |call trace|softlockup|hungtask|out of memory" \ + | grep -Ev "debug|if necessary|Subscription" + echo "--- end ---" + return 1 + fi + + return 0 +} + +# ============================================================================= +# 测试框架主函数(对标 case_lib.c 的 test_main) +# ============================================================================= + +# 用例需要实现的函数(由用例脚本定义): +# setup() - 测试初始化 +# cleanup() - 测试清理 +# run_test() - 执行测试,参数为测试用例索引 $1 (从0开始) +# +# 用例需要定义的变量: +# tcnt - 测试用例总数 + +_print_usage() +{ + echo "用法: ${0##*/} [选项]" + echo "选项:" + echo " -h 显示帮助信息" + echo " -i 运行迭代次数 (默认: 1, 0表示无限循环)" + echo "" +} + +test_main() +{ + local iterations=1 + local opt + + # 解析命令行参数 + while getopts "hi:" opt; do + case $opt in + h) + _print_usage + exit 0 + ;; + i) + iterations=$OPTARG + ;; + *) + _print_usage + exit 1 + ;; + esac + done + + # 检查用例是否定义了必要的变量和函数 + if [ -z "$tcnt" ] || [ "$tcnt" -le 0 ] 2>/dev/null; then + tst_brk $TFAIL "用例未定义 tcnt 或 tcnt <= 0" + fi + + local iter_str + if [ "$iterations" -eq 0 ]; then + iter_str="无限" + elif [ "$iterations" -eq 1 ]; then + iter_str="1" + else + iter_str="多次(${iterations})" + fi + + echo "========================================" + echo "测试名称: ${tst_name}" + echo "测试用例数: ${tcnt}" + echo "迭代次数: ${iter_str}" + echo "========================================" + echo "" + + # 清理内核日志(前置步骤) + clear_dmesg + + # 执行初始化 + setup + + # 执行测试迭代 + local iter=0 + while [ "$iterations" -eq 0 ] || [ "$iter" -lt "$iterations" ]; do + if [ "$iterations" -ne 1 ]; then + echo "" + echo "--- 迭代 #$((iter + 1)) ---" + fi + + # 遍历所有测试用例 + local i=0 + while [ "$i" -lt "$tcnt" ]; do + run_test "$i" + i=$((i + 1)) + done + + iter=$((iter + 1)) + done + + # 执行清理 + cleanup + + # 注意: check_dmesg 不在框架层自动调用,避免异常测试场景误判 + # 用例如需检查dmesg,请在 cleanup() 或 run_test() 中主动调用 check_dmesg + + # 输出测试汇总 + echo "" + echo "========================================" + echo "测试汇总:" + echo " 总计: $((tst_pass + tst_fail))" + echo " 通过: ${tst_pass}" + echo " 失败: ${tst_fail}" + echo "========================================" + + # 返回状态码 + if [ "$tst_fail" -gt 0 ]; then + exit 1 + else + exit 0 + fi +} diff --git a/tools/tos_test.sh b/tools/tos_test.sh new file mode 100755 index 0000000..a6b74b6 --- /dev/null +++ b/tools/tos_test.sh @@ -0,0 +1,629 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2026 Tencent. All Rights Reserved. +# Author: shankslong +# Date: 2026/03/11 +# +# TencentOS 测试套执行框架 +# 使用方式: ./tos_test.sh <命令> [参数] +# +# 命令列表: +# pkg 安装本测试套依赖的软件包 +# compile 编译本测试套 +# test [tag] 执行测试(可选指定标签) +# case <名称> 执行单个用例 +# clean 清理编译产物 +# help 显示帮助信息 +# +# 对标 case_lib.sh,提供统一的测试套管理、编译、执行、日志等基础能力。 + +# ============================================================================= +# 防止重复source +# ============================================================================= + +[ -n "$_TOS_TEST_SH_LOADED" ] && { return 0 2>/dev/null || true; } +_TOS_TEST_SH_LOADED=1 + +# ============================================================================= +# 日志级别定义(对标 case_lib.sh 的 TPASS/TFAIL/TCONF/TWARN/TINFO) +# ============================================================================= + +readonly LOG_PASS=0 # 成功 +readonly LOG_FAIL=1 # 失败 +readonly LOG_INFO=2 # 信息 +readonly LOG_WARN=3 # 警告 +readonly LOG_PROMPT=4 # 提示 + +# ============================================================================= +# 全局配置变量 +# ============================================================================= + +# 测试套顶层目录 +readonly G_SUITE_TOP_DIR="$(cd "$(dirname "$0")/.." && pwd)" + +# 日志根目录 +readonly G_LOG_ROOT='/data/log/os_suites' + +# 加载测试套配置文件 +if [ -f "${G_SUITE_TOP_DIR}/control" ]; then + source "${G_SUITE_TOP_DIR}/control" +else + echo "ERROR: control 文件不存在: ${G_SUITE_TOP_DIR}/control" + exit 1 +fi + +# 测试套名称 +readonly G_SUITE_NAME="${CONTROL_SUITE_NAME:-$(basename "$G_SUITE_TOP_DIR")}" + +# 日志目录和文件 +readonly G_SUITE_LOG_DIR="${G_LOG_ROOT}/${G_SUITE_NAME}" +readonly G_SUITE_LOG_FILE="${G_SUITE_LOG_DIR}/${G_SUITE_NAME}.log" +readonly G_SUITE_COMPILE_LOG="${G_SUITE_LOG_DIR}/${G_SUITE_NAME}.log.compile" +readonly G_SUITE_RESULT_FILE="${G_SUITE_LOG_DIR}/${G_SUITE_NAME}.result" + +# 源码和安装路径 +readonly G_SUITE_SRC="${G_SUITE_TOP_DIR}/${G_SUITE_NAME}_src" +readonly G_SUITE_BIN="${G_SUITE_TOP_DIR}/${G_SUITE_NAME}" + +# 用例超时时间(默认3600秒) +readonly G_CASE_TIMEOUT="${CONTROL_CASE_TIMEOUT:-3600}" + +# ============================================================================= +# 全局测试状态 +# ============================================================================= + +g_test_count=0 # 测试用例计数 +g_pass_count=0 # 通过计数 +g_fail_count=0 # 失败计数 +g_skip_count=0 # 跳过计数 + +# ============================================================================= +# 加载前置/后置脚本 +# ============================================================================= + +[ -f "${G_SUITE_TOP_DIR}/pre_init.sh" ] && source "${G_SUITE_TOP_DIR}/pre_init.sh" +[ -f "${G_SUITE_TOP_DIR}/post_uninit.sh" ] && source "${G_SUITE_TOP_DIR}/post_uninit.sh" + +# ============================================================================= +# 日志输出函数(对标 case_lib.sh 的 tst_res) +# ============================================================================= + +# _log_to_file - 写入日志文件(内部函数) +# @msg: 日志内容 +_log_to_file() +{ + [ ! -d "$(dirname "$G_SUITE_LOG_FILE")" ] && mkdir -p "$(dirname "$G_SUITE_LOG_FILE")" + echo "$(date '+%Y-%m-%d %H:%M:%S') >> $*" >> "$G_SUITE_LOG_FILE" +} + +# log_res - 统一日志输出函数(对标 tst_res) +# @level: 日志级别 (LOG_PASS/LOG_FAIL/LOG_INFO/LOG_WARN/LOG_PROMPT) +# @msg: 日志内容 +log_res() +{ + local level=$1 + shift + local msg="$*" + local prefix color + + case $level in + $LOG_PASS) + prefix="PASS" + color="\033[1;32m" # 绿色 + g_pass_count=$((g_pass_count + 1)) + ;; + $LOG_FAIL) + prefix="FAIL" + color="\033[1;31m" # 红色 + g_fail_count=$((g_fail_count + 1)) + ;; + $LOG_INFO) + prefix="INFO" + color="\033[1;34m" # 蓝色 + ;; + $LOG_WARN) + prefix="WARN" + color="\033[1;33m" # 黄色 + ;; + $LOG_PROMPT) + prefix="" + color="\033[1;35m" # 紫色 + ;; + *) + prefix="????" + color="\033[0m" + ;; + esac + + if [ -n "$prefix" ]; then + echo -e "${color}${prefix}: ${msg}\033[0m" + _log_to_file "${prefix}: ${msg}" + else + echo -e "${color}${msg}\033[0m" + _log_to_file "${msg}" + fi +} + +# 便捷日志函数(封装 log_res) +log_pass() { log_res $LOG_PASS "$@"; } +log_fail() { log_res $LOG_FAIL "$@"; } +log_info() { log_res $LOG_INFO "$@"; } +log_warn() { log_res $LOG_WARN "$@"; } +log_prompt() { log_res $LOG_PROMPT "$@"; } + +# ============================================================================= +# 系统日志管理函数 +# ============================================================================= + +# clear_dmesg - 清理内核日志缓冲区 +clear_dmesg() +{ + dmesg -c > /dev/null 2>&1 +} + +# check_dmesg - 检查内核日志异常 +# @log_file: 异常时保存dmesg的文件路径(可选) +# 返回: 0-无异常, 1-发现异常 +check_dmesg() +{ + local log_file="${1:-}" + + if dmesg | grep -iE "oops|warning| bug |call trace|softlockup|hungtask|out of memory" \ + | grep -Evq "debug|if necessary|Subscription|random"; then + log_fail "dmesg check: 发现异常内核日志" + [ -n "$log_file" ] && dmesg > "${log_file}.dmesg" + return 1 + fi + return 0 +} + +# clear_system_cache - 清理系统缓存和跟踪 +clear_system_cache() +{ + echo 3 > /proc/sys/vm/drop_caches 2>/dev/null + echo 0 > /sys/kernel/debug/tracing/tracing_on 2>/dev/null + echo 0 > /sys/kernel/debug/tracing/function_profile_enabled 2>/dev/null + echo > /sys/kernel/debug/tracing/trace 2>/dev/null + echo nop > /sys/kernel/debug/tracing/current_tracer 2>/dev/null +} + +# ============================================================================= +# 依赖包安装函数 +# ============================================================================= + +# _install_packages - 安装测试套依赖包 +# 返回: 0-成功, 非0-失败 +_install_packages() +{ + local pkg_cmd="${CONTROL_PKG_CMD:-yum install -y}" + local err_pkgs="" + local start_time end_time pkg + + log_info "开始安装依赖包..." + start_time=$(date +%s) + + # 导入EPEL GPG密钥(避免交互) + rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL* 2>/dev/null + + for pkg in $CONTROL_PRE_PKG; do + log_info "正在安装: $pkg ..." + if $pkg_cmd $pkg >> "$G_SUITE_LOG_FILE" 2>&1; then + log_pass "$pkg 安装成功" + else + log_fail "$pkg 安装失败" + err_pkgs="$err_pkgs $pkg" + fi + done + + end_time=$(date +%s) + _log_to_file "依赖包安装完成 (耗时: $((end_time - start_time))秒)" + + [ -n "$err_pkgs" ] && { log_fail "以下包安装失败:$err_pkgs"; return 1; } + return 0 +} + +# ============================================================================= +# 编译安装函数 +# ============================================================================= + +# _compile_suite - 编译测试套 +# 返回: 0-成功, 非0-失败 +_compile_suite() +{ + local start_time end_time compile_ret=0 + local cmd_count cmd_index sub_cmd + + log_info "开始编译测试套..." + start_time=$(date +%s) + + cd "$G_SUITE_TOP_DIR" || return 1 + + # 执行编译前置函数 + type pre_init_compile >/dev/null 2>&1 && pre_init_compile + + # 切换到源码目录 + cd "$G_SUITE_SRC" || { log_fail "源码目录不存在: $G_SUITE_SRC"; return 1; } + + # 解析并执行编译命令(分号分隔) + cmd_count=$(echo "$CONTROL_COMPILE_CMD" | awk -F';' '{print NF}') + cmd_index=0 + + while [ $cmd_index -lt $cmd_count ]; do + cmd_index=$((cmd_index + 1)) + sub_cmd=$(echo "$CONTROL_COMPILE_CMD" | awk -F';' "{print \$$cmd_index}") + + # 处理configure命令,添加--prefix + echo "$sub_cmd" | grep -q "configure" && sub_cmd="$sub_cmd --prefix=${G_SUITE_BIN}" + + # 处理cd命令,确保相对于源码目录 + echo "$sub_cmd" | grep -q "cd " && cd "$G_SUITE_SRC" + + log_info "执行: $sub_cmd" + if eval "$sub_cmd" >> "$G_SUITE_LOG_FILE" 2>&1; then + log_pass "$sub_cmd" + else + log_fail "$sub_cmd" + compile_ret=1 + fi + done + + cd "$G_SUITE_TOP_DIR" || true + + # 执行编译后置函数 + type post_uninit_compile >/dev/null 2>&1 && post_uninit_compile + + # 保存编译日志 + cp -f "$G_SUITE_LOG_FILE" "$G_SUITE_COMPILE_LOG" 2>/dev/null + + end_time=$(date +%s) + _log_to_file "编译完成 (耗时: $((end_time - start_time))秒)" + + [ $compile_ret -ne 0 ] && { log_fail "编译失败,请检查 CONTROL_COMPILE_CMD 配置"; return 1; } + return 0 +} + +# ============================================================================= +# 用例解析函数 +# ============================================================================= + +# _parse_case_line - 解析用例行,提取用例名、目录、命令 +# @case_str: 用例配置行 +# 输出: 设置全局变量 _case_name, _case_dir, _case_cmd +_parse_case_line() +{ + local case_str="$1" + local field_count + + field_count=$(echo "$case_str" | awk -F';' '{print NF}') + + if [ $field_count -ge 3 ]; then + # 格式1: 用例名; cd路径; 执行命令 + _case_name=$(echo "$case_str" | awk -F';' '{print $1}' | xargs) + _case_dir=$(echo "$case_str" | awk -F';' '{print $2}') + _case_cmd=$(echo "$case_str" | awk -F"$_case_dir;" '{print $2}') + else + # 格式2: 用例名 执行命令(默认在testcases/bin目录) + _case_name=$(echo "$case_str" | awk '{print $1}') + _case_dir="cd ${G_SUITE_NAME}/testcases/bin" + _case_cmd="${case_str#* }" + fi +} + +# ============================================================================= +# 测试套特殊处理函数 +# ============================================================================= + +# _suite_specific_cleanup - 测试套特定的清理操作 +# @suite_name: 测试套名称 +_suite_specific_cleanup() +{ + case "$1" in + ltp) + ip netns delete ltp_ns 2>/dev/null + ip link delete ltp_ns_veth1 2>/dev/null + ip link delete ltp_ns_veth2 2>/dev/null + pkill -9 tst_ns_create 2>/dev/null + rm -rf /var/run/netns/ltp_ns 2>/dev/null + ;; + esac +} + +# _suite_specific_result_check - 测试套特定的结果检查 +# @suite_name: 测试套名称 +# @log_file: 用例日志文件 +# 返回: 0-通过, 非0-失败 +_suite_specific_result_check() +{ + local suite_name="$1" + local log_file="$2" + + case "$suite_name" in + perf) + grep -qE ": Skip|: FAILED" "$log_file" 2>/dev/null && return 1 + [ ! -s "$log_file" ] && return 1 + ;; + libfuse) + grep -qE "SKIPPED|FAILED" "$log_file" 2>/dev/null && return 1 + ;; + cifstest|nfstest|xfstest|ext4test|fusetest_passthrough_hp|fusetest_passthrough_ll) + grep -qE "Not run: " "$log_file" 2>/dev/null && return 1 + sleep 5 + ;; + esac + return 0 +} + +# ============================================================================= +# 单个用例执行函数 +# ============================================================================= + +# _execute_single_case - 执行单个测试用例 +# @case_name: 用例名称 +# @case_dir: 用例目录(cd命令) +# @case_cmd: 用例执行命令 +# @result_file: 结果文件路径 +# 返回: 0-通过, 非0-失败 +_execute_single_case() +{ + local case_name="$1" + local case_dir="$2" + local case_cmd="$3" + local result_file="$4" + local case_log_file="${G_SUITE_LOG_DIR}/${case_name}" + local case_start_time case_end_time case_time case_result + + export case_log_file + + _log_to_file "测试用例: $case_name | 命令: $case_dir ; $case_cmd" + + # 前置清理 + clear_dmesg + clear_system_cache + + # 切换到用例目录 + cd "$G_SUITE_TOP_DIR" || return 1 + eval "$case_dir" || { log_fail "切换目录失败: $case_dir"; return 1; } + + # 执行用例(带超时控制) + case_start_time=$(date +%s) + timeout -s KILL "$G_CASE_TIMEOUT" bash -c "$case_cmd" >> "$case_log_file" 2>&1 + case_result=$? + case_end_time=$(date +%s) + case_time=$((case_end_time - case_start_time)) + + # 测试套特定处理 + _suite_specific_cleanup "$G_SUITE_NAME" + _suite_specific_result_check "$G_SUITE_NAME" "$case_log_file" || case_result=1 + + # 检查dmesg + check_dmesg "$case_log_file" + + # 记录结果 + if [ $case_result -eq 0 ]; then + echo "PASS: $case_name $case_time" >> "$result_file" + log_pass "$case_name (${case_time}s)" + elif [ $case_result -eq 32 ]; then + echo "SKIP: $case_name $case_time" >> "$result_file" + log_info "$case_name SKIP (${case_time}s) - 环境不满足/配置跳过" + g_skip_count=$((g_skip_count + 1)) + else + echo "FAIL: $case_name $case_time" >> "$result_file" + log_fail "$case_name (${case_time}s)" + [ $case_result -eq 137 ] && echo "FAIL: 用例超时 (${G_CASE_TIMEOUT}秒)" >> "$case_log_file" + fi + + cd "$G_SUITE_TOP_DIR" || true + return $case_result +} + +# ============================================================================= +# 测试执行函数 +# ============================================================================= + +# _run_all_tests - 执行所有测试用例 +# @tag: 可选的过滤标签 +# 返回: 0-全部通过, 非0-存在失败 +_run_all_tests() +{ + local tag="${1:-}" + local cases_file="${G_SUITE_TOP_DIR}/baseline/cases" + local result_file="$G_SUITE_RESULT_FILE" + local case_str + + # 根据标签选择用例文件 + if [ -n "$tag" ] && [ -f "${G_SUITE_TOP_DIR}/baseline/cases_${tag}" ]; then + cases_file="${G_SUITE_TOP_DIR}/baseline/cases_${tag}" + result_file="${G_SUITE_LOG_DIR}/${G_SUITE_NAME}_${tag}.result" + fi + + log_info "开始执行测试..." + log_info "用例文件: $cases_file" + log_info "结果文件: $result_file" + + [ ! -f "$cases_file" ] && { log_fail "用例文件不存在: $cases_file"; return 1; } + + # 执行测试前置函数 + cd "$G_SUITE_TOP_DIR" || return 1 + type pre_init_test >/dev/null 2>&1 && pre_init_test "$@" + + g_test_count=0 + g_pass_count=0 + g_fail_count=0 + g_skip_count=0 + + # 遍历执行每个用例(使用 while read 替代 sed 逐行读取) + while IFS= read -r case_str || [ -n "$case_str" ]; do + # 只处理以数字或字母开头的行 + [[ "$case_str" =~ ^[0-9a-zA-Z] ]] || continue + + _parse_case_line "$case_str" + g_test_count=$((g_test_count + 1)) + + _execute_single_case "$_case_name" "$_case_dir" "$_case_cmd" "$result_file" + + # 记录已完成用例(用于断点续跑) + if echo "$cases_file" | grep -q "ignore"; then + echo "$case_str" >> "/data/log/os_suites/done_ignore_${G_SUITE_NAME}" + echo "[$G_SUITE_NAME] $case_str" >> "/data/log/os_suites/done_ignore" + else + echo "$case_str" >> "/data/log/os_suites/done_baseline_${G_SUITE_NAME}" + echo "[$G_SUITE_NAME] $case_str" >> "/data/log/os_suites/done_baseline" + fi + done < "$cases_file" + + cd "$G_SUITE_TOP_DIR" || true + + # 执行测试后置函数 + type post_uninit_test >/dev/null 2>&1 && post_uninit_test + + # 输出测试汇总 + echo "" + log_prompt "=========================================" + log_prompt "测试汇总: ${G_SUITE_NAME}" + log_prompt " 总计: $g_test_count" + log_prompt " 通过: $g_pass_count" + log_prompt " 失败: $g_fail_count" + log_prompt " 跳过: $g_skip_count" + log_prompt "=========================================" + + [ $g_fail_count -gt 0 ] && { log_fail "测试套 $G_SUITE_NAME 存在失败用例"; return 1; } + return 0 +} + +# _run_single_test - 执行单个指定用例 +# @case_name: 用例名称 +# 返回: 0-通过, 非0-失败 +_run_single_test() +{ + local target_case="$1" + local cases_file="${G_SUITE_TOP_DIR}/baseline/cases" + local match_count case_str ret + + log_info "执行单个用例: $target_case" + + [ ! -f "$cases_file" ] && { log_fail "用例文件不存在: $cases_file"; return 1; } + + # 匹配用例配置行 + match_count=$(grep -cE "^${target_case} |^${target_case};" "$cases_file") + + if [ "$match_count" -ne 1 ]; then + echo "FAIL: $target_case 0 case_cmdline_not_unique" >> "$G_SUITE_RESULT_FILE" + log_fail "用例 $target_case 在 $cases_file 中不唯一 (匹配数: $match_count)" + return 1 + fi + + case_str=$(grep -E "^${target_case} |^${target_case};" "$cases_file") + + # 执行测试前置函数 + cd "$G_SUITE_TOP_DIR" || return 1 + type pre_init_test >/dev/null 2>&1 && pre_init_test + + # 解析并执行用例 + _parse_case_line "$case_str" + _execute_single_case "$_case_name" "$_case_dir" "$_case_cmd" "$G_SUITE_RESULT_FILE" + ret=$? + + cd "$G_SUITE_TOP_DIR" || true + + # 执行测试后置函数 + type post_uninit_test >/dev/null 2>&1 && post_uninit_test + + [ $ret -ne 0 ] && { log_fail "用例 $target_case 执行失败"; return 1; } + return 0 +} + +# ============================================================================= +# 清理函数 +# ============================================================================= + +# _clean_suite - 清理测试套编译产物 +_clean_suite() +{ + log_info "清理测试套..." + + rm -rf "$G_SUITE_BIN" + + if [ -d "$G_SUITE_SRC" ]; then + cd "$G_SUITE_SRC" || return 1 + log_info "执行: $CONTROL_CLEAN_CMD" + if eval "$CONTROL_CLEAN_CMD" >> "$G_SUITE_LOG_FILE" 2>&1; then + log_pass "清理命令执行成功" + else + log_fail "清理命令执行失败" + fi + fi + + type post_uninit_compile >/dev/null 2>&1 && post_uninit_compile +} + +# ============================================================================= +# 帮助信息函数 +# ============================================================================= + +_print_help() +{ + cat << EOF + +TencentOS 测试套执行框架 - ${G_SUITE_NAME} + +用法: $0 <命令> [参数] + +命令: + pkg 安装本测试套依赖的软件包 + compile 编译本测试套 + test [tag] 执行测试(可选指定标签过滤) + case <用例名> 执行单个指定用例 + clean 清理编译产物 + help 显示本帮助信息 + +示例: + $0 pkg # 安装依赖包 + $0 compile # 编译测试套 + $0 test # 执行所有用例 + $0 test Hygon_C86_7285 # 执行指定标签用例 + $0 case accept03 # 执行单个用例 accept03 + $0 clean # 清理编译产物 + +配置文件: + control # 测试套配置(必需) + pre_init.sh # 前置初始化脚本 + post_uninit.sh # 后置清理脚本 + baseline/cases # 用例列表(必需) + +日志目录: + ${G_SUITE_LOG_DIR}/ + +EOF +} + +# ============================================================================= +# 主入口函数 +# ============================================================================= + +tos_test_main() +{ + local cmd="${1:-help}" + shift 2>/dev/null || true + + mkdir -p "$G_SUITE_LOG_DIR" + + case "$cmd" in + pkg) _install_packages ;; + compile) _compile_suite ;; + test) _run_all_tests "$@" ;; + case) + [ -z "$1" ] && { log_fail "请指定用例名称"; _print_help; exit 1; } + _run_single_test "$1" + ;; + clean) _clean_suite ;; + *) _print_help ;; + esac + + exit $g_fail_count +} + +# ============================================================================= +# 脚本入口 +# ============================================================================= + +[ "${BASH_SOURCE[0]}" == "${0}" ] && tos_test_main "$@" -- Gitee