以下do-tests测试脚本执行完整测试。全部用例成功421个,失败10个。有打印信息可知,每个测试用例分为三个步骤:pre/test/post。

$ cd strongswan-5.8.1/testing
$ sudo ./do-tests
Guest kernel : 5.2.11
strongSwan   : 5.8.1
Date         : 20190917-0126-52[ ok ]  1 af-alg/alg-camellia: pre..test..post
...
[FAIL]  429 tnc/tnccs-20-pts-no-ecc: pre..test..post
[ ok ]  430 tnc/tnccs-20-tls: pre..test..post
[ ok ]  431 tnc/tnccs-dynamic: pre..test..postPassed : 421
Failed : 10The results are available in /srv/strongswan-testing/testresults/20190917-0126-52
or via the link http://192.168.0.150/testresults/20190917-0126-52Finished : 20190917-0213-51

测试准备

首先,获取到各个虚拟主机的IP地址。两个变量ipv4_KaTeX parse error: Expected group after '_' at position 12: {host}和ipv6_̲{host}中保存的是各个虚拟主机的第一个网卡的IPv4和IPv6地址;另外,对于具有两个网卡的虚拟网关moon和sun,以及虚拟主机alice、carol和dave,增加后缀1来表示其第二个网卡的IPv4和IPv6地址。例如,对于moon网关,ipv4_moon1和ipv6_moon1分别表示其IPv4和IPv6地址。

    for host in $STRONGSWANHOSTSdoeval ipv4_${host}="`echo $HOSTNAMEIPV4 | sed -n -e "s/^.*${host},//gp" | awk -F, '{ print $1 }' | awk '{ print $1 }'`"eval ipv6_${host}="`echo $HOSTNAMEIPV6 | sed -n -e "s/^.*${host},//gp" | awk -F, '{ print $1 }' | awk '{ print $1 }'`"case $host inmoon)eval ipv4_moon1="`echo $HOSTNAMEIPV4 | sed -n -e "s/^.*${host},//gp" | awk -F, '{ print $2 }' | awk '{ print $1 }'`"eval ipv6_moon1="`echo $HOSTNAMEIPV6 | sed -n -e "s/^.*${host},//gp" | awk -F, '{ print $2 }' | awk '{ print $1 }'`";;sun)eval ipv4_sun1="`echo $HOSTNAMEIPV4 | sed -n -e "s/^.*${host},//gp" | awk -F, '{ print $2 }' | awk '{ print $1 }'`"eval ipv6_sun1="`echo $HOSTNAMEIPV6 | sed -n -e "s/^.*${host},//gp" | awk -F, '{ print $2 }' | awk '{ print $1 }'`";;alice)eval ipv4_alice1="`echo $HOSTNAMEIPV4 | sed -n -e "s/^.*${host},//gp" | awk -F, '{ print $2 }' | awk '{ print $1 }'`"eval ipv6_alice1="`echo $HOSTNAMEIPV6 | sed -n -e "s/^.*${host},//gp" | awk -F, '{ print $2 }' | awk '{ print $1 }'`";;venus);;bob);;carol)eval ipv4_carol1="`echo $HOSTNAMEIPV4 | sed -n -e "s/^.*${host},//gp" | awk -F, '{ print $2 }' | awk '{ print $1 }'`"eval ipv6_carol1="`echo $HOSTNAMEIPV6 | sed -n -e "s/^.*${host},//gp" | awk -F, '{ print $2 }' | awk '{ print $1 }'`";;dave)eval ipv4_dave1="`echo $HOSTNAMEIPV4 | sed -n -e "s/^.*${host},//gp" | awk -F, '{ print $2 }' | awk '{ print $1 }'`"eval ipv6_dave1="`echo $HOSTNAMEIPV6 | sed -n -e "s/^.*${host},//gp" | awk -F, '{ print $2 }' | awk '{ print $1 }'`";;winnetou);;esacdone

由于测试过程将使用SSH登陆到待测试的虚拟主机或者网关上执行命令,在这里先行打开各个主机(STRONGSWANHOSTS)的SSH通道,令其在后台运行。当测试完成退出时,使用kill命令终止此SSH后台通道进程。

    # open ssh sessions#for host in $STRONGSWANHOSTSdossh $SSHCONF -N root@`eval echo \\\$ipv4_$host` >/dev/null 2>&1 &eval ssh_pid_$host="`echo $!`"do_on_exit kill `eval echo \\\$ssh_pid_$host`done

SSH通道打开之后,就可使用如下的命令执行指定主机上面的命令,如下,登录到虚拟主机winnetou,指定uname -r命令,将结果保存在变量内核版本KERNELVERSION中。

    ############################################################################### determine actual software versions#[ -f $SHAREDDIR/.strongswan-version ] && SWANVERSION=`cat $SHAREDDIR/.strongswan-version`KERNELVERSION=`ssh $SSHCONF root@\$ipv4_winnetou uname -r 2>/dev/null`

测试目标

使用:./do-tests 开始测试,其中testnames为测试名称,即存放测试配置的子目录,所有的测试都存放于目录strongswan-5.8.1/testing/tests/下(变量DEFAULTTESTSDIR),可指定多个测试名称。如果不指定,将进行全部的测试。

    DEFAULTTESTSDIR=$TESTDIR/testing/tests# enter specific test directory#if [ $# -gt 0 ]thenTESTS=$(printf "%s\n" $* | sort -u)elseTESTS=$(ls $DEFAULTTESTSDIR)fi

如下方式指定测试名称:

./do-tests ikev2/net2net-psk ikev2/net2net-cert

StrongSwan的测试用例都是分两级目录保存的,如以上指定测试名称的情况,第二级目录名称(如net2net-psk 何net2net-cert)才是真正的测试名称。这种情况下SUBTESTS和SUBTESTS和SUBTESTS和SUBDIR变量是不相等的。对于未指定测试用例的情况,两个变量相等,即以下的第一个分支,此情况下,取得子分支下的所有测试用例名称,赋值于变量SUBTESTS。

    for SUBDIR in $TESTSdoSUBTESTS="`basename $SUBDIR`"if [ $SUBTESTS = $SUBDIR ]thenSUBTESTS="`ls $DEFAULTTESTSDIR/$SUBDIR`"elseif [[ $SUBTESTS == *'*'* ]]thenSUBTESTS="`basename -a $DEFAULTTESTSDIR/$SUBDIR`"fiSUBDIR="`dirname $SUBDIR`"fi

所以,对于命令行指定了测试用例的情况,变量SUBTESTS每次循环仅有一个测试例;相反,对于命令行未指定测试用例的情况,变量SUBTESTS可能包含多个测试用例。嵌套一个循环处理SUBTESTS变量中的测试用例。

测试流程要求每个测试用例包含以下五个文件,否则出错。

            for name in $SUBTESTSdotestname=$SUBDIR/$name[ -f $DEFAULTTESTSDIR/${testname}/description.txt ] || die "!! File 'description.txt' is missing"[ -f $DEFAULTTESTSDIR/${testname}/test.conf ]       || die "!! File 'test.conf' is missing"[ -f $DEFAULTTESTSDIR/${testname}/pretest.dat ]     || die "!! File 'pretest.dat' is missing"[ -f $DEFAULTTESTSDIR/${testname}/posttest.dat ]    || die "!! File 'posttest.dat' is missing"[ -f $DEFAULTTESTSDIR/${testname}/evaltest.dat ]    || die "!! File 'evaltest.dat' is missing"

以下的脚本load-config使用scp命令将代码目录:strongswan-5.8.1/testing/hosts/$host/etc下的所有配置文件发送到相应的虚拟主机上,测试过程中将使会用到。

                $DIR/scripts/load-testconfig $testnamesource $TESTDIR/test.conf

随后,如果配置了TCPDUMPHOSTS主机(此变量配置在以上的$TESTDIR/test.conf文件中),启动tcpdump在后台运行,如果未指定了tcpdump运行的接口,默认使用eth0。由于整个测试过程都在使用ssh与虚拟主机通信,在tcpdump命令中排除ssh端口。

             # run tcpdump in the background#if [ "$TCPDUMPHOSTS" != "" ]thenecho -e "TCPDUMP\n" >> $CONSOLE_LOG 2>&1for host_iface in $TCPDUMPHOSTSdohost=`echo $host_iface | awk -F ":" '{print $1}'`iface=`echo $host_iface | awk -F ":" '{if ($2 != "") { print $2 } else { printf("eth0") }}'`tcpdump_cmd="tcpdump -l $TCPDUMP_IM -i $iface not port ssh and not port domain >/tmp/tcpdump.log 2>/tmp/tcpdump.err.log &"echo "$(print_time)${host}# $tcpdump_cmd" >> $CONSOLE_LOGssh $SSHCONF root@`eval echo \\\$ipv4_$host '$tcpdump_cmd'`eval TDUP_${host}="true"donefi

如果在测试用例的配置文件$TESTDIR/test.conf中指定了DBHOSTS,此处将创建DBDIR目录,并且使用mount挂载一块5M大小的内存文件系统到此目录。并不是所有的测试用例都需要。

             DBDIR=/etc/db.d# create database directory in RAM#for host in $DBHOSTSdoeval HOSTLOGIN=root@\$ipv4_${host}ssh $SSHCONF $HOSTLOGIN "mkdir -p $DBDIR; mount -t ramfs -o size=5m ramfs $DBDIR" >/dev/null 2>&1ssh $SSHCONF $HOSTLOGIN "chgrp www-data $DBDIR; chmod g+w $DBDIR" >/dev/null 2>&1done

以下部分执行与测试相关的系统清理工作,避免影响之后要开始的测试用例。使用conntrack -F命令清空各个虚拟主机上的连接跟踪条目。使用ip xfrm命令清空虚拟主机中的SADB和SPDB。

             # flush conntrack table on all hosts#for host in $STRONGSWANHOSTSdossh $SSHCONF root@`eval echo \\\$ipv4_$host` 'conntrack -F' >/dev/null 2>&1done########################################################################### flush IPsec state on all hosts#for host in $STRONGSWANHOSTSdossh $SSHCONF root@`eval echo \\\$ipv4_$host` 'ip xfrm state flush; ip xfrm policy flush' >/dev/null 2>&1done

之后,开始执行测试用例的特定任务。

执行用例的预测试pretest.dat

以下为do-tests文件中执行pre-test阶段命令的脚本:

        # execute pre-test commands#echo -n "pre.."echo -e "\nPRE-TEST\n" >> $CONSOLE_LOG 2>&1eval `awk -F "::" '{if ($1 !~ /^#.*/ && $2 != ""){printf("echo \"$(print_time)%s# %s\"; ", $1, $2)printf("ssh \044SSHCONF root@\044ipv4_%s \"%s\"; ", $1, $2)printf("echo;\n")}}' $TESTDIR/pretest.dat` >> $CONSOLE_LOG 2>&1

对于af-alg/alg-camellia测试用例而言,其pretest.dat文件如下。在预测试pre-test阶段,备份moon和carol主机的iptables配置。启动strongswan。使用脚本expect-connection检测名称为net的连接(carol主机上为home)是否建立,超过5秒钟检测不到,打印失败信息。swanctl初始化一个名称为home的子连接。

  1 moon::iptables-restore < /etc/iptables.rules2 carol::iptables-restore < /etc/iptables.rules3 moon::systemctl start strongswan4 carol::systemctl start strongswan5 moon::expect-connection net6 carol::expect-connection home7 carol::swanctl --initiate --child home 2> /dev/null

执行测试用例并检验结果

在do_tests脚本中的以下代码负责执行测试用例的evaltest.dat文件中定义的命令,其中每一行由四个部分组成,使用双冒号::做间隔。四个部分分别表示:host、command、pattern和hit(期待结果)。这里需要注意的是,如果第二个字段的命令为tcpdump,这里并不执行此命令,而是检查文件/tmp/tcpdump.log中的内容。

        eval `awk -F "::" '{host=$1command=$2pattern=$3hit=$4if (host ~ /^#.*/ || command == ""){next}printf("cmd_err=\044(tempfile -p test -s err); ")printf("cmd_out=\044(tempfile -p test -s out); ")if (command == "tcpdump"){printf("if [ \044TDUP_%s == \"true\" ]; then stop_tcpdump %s; fi; \n", host, host)printf("ssh \044SSHCONF root@\044ipv4_%s cat /tmp/tcpdump.log > \044cmd_out; ", host)}else{printf("ssh \044SSHCONF root@\044ipv4_%s %s >\044cmd_out 2>\044cmd_err; ",  host, command)}printf("cmd_res=\044(cat \044cmd_out | grep \"%s\"); ", pattern)printf("cmd_exit=\044?; ")printf("cmd_fail=0; ")if (hit ~ /^[0-9]+$/){printf("if [ \044(echo \"\044cmd_res\" | wc -l) -ne %d ] ", hit)}else{printf("if [ \044cmd_exit -eq 0 -a \"%s\" = \"NO\"  ] ", hit)printf("|| [ \044cmd_exit -ne 0 -a \"%s\" = \"YES\" ] ", hit)}printf("; then STATUS=\"failed\"; cmd_fail=1; fi; \n")printf("if [ \044cmd_fail -ne 0 ]; then echo \"~~~~~~~ FAIL ~~~~~~~\"; fi; \n")if (command == "tcpdump"){printf("echo \"$(print_time)%s# cat /tmp/tcpdump.log | grep \047%s\047  [%s]\"; ", host, pattern, hit)}else{printf("echo \"$(print_time)%s# %s | grep \047%s\047  [%s]\"; ", host, command, pattern, hit)}printf("if [ -n \"\044cmd_res\" ]; then echo \"\044cmd_res\"; fi; \n")printf("cat \044cmd_err; \n")printf("if [ \044cmd_fail -ne 0 ]; then \n")printf("if [ -s \044cmd_out ]; then echo \"~~ output ~~~~~~~~~~\"; \n")printf("if [ \"\044verbose\" == \"YES\" ]; then cat \044cmd_out;\n")printf("else cat \044cmd_out | head; fi; fi; \n")printf("echo \"~~~~~~~~~~~~~~~~~~~~\"; fi; \n")printf("rm -f -- \044cmd_out \044cmd_err; \n")printf("echo; ")}' $TESTDIR/evaltest.dat` >> $CONSOLE_LOG 2>&1

测试用例af-alg/alg-camellia的测试文件evaltest.dat如下所示,如第一行所示,在carol主机上执行ping命令,期待alice返回的信息符合pattern:(128 bytes from PH_IP_ALICE: icmp_.eq=1)。

      carol::ping -c 1 -s 120 -p deadbeef PH_IP_ALICE::128 bytes from PH_IP_ALICE: icmp_.eq=1::YES...moon:: ip xfrm state::enc cbc(camellia)::YEScarol::ip xfrm state::enc cbc(camellia)::YESmoon::tcpdump::IP carol.strongswan.org > moon.strongswan.org: ESP.*length 208::YESmoon::tcpdump::IP moon.strongswan.org > carol.strongswan.org: ESP.*length 208::YES

停止后台TCPDUMP

以下函数用于停止之前在后台启动的tcpdump命令。

         # stop tcpdump#function stop_tcpdump {# wait for packets to get processed, but don't wait longer than 1seval ssh $SSHCONF root@\$ipv4_${1} "\"i=100; while [ \\\$i -gt 0 ]; do pkill -USR1 tcpdump; tail -1 /tmp/tcpdump.err.log | perl -n -e '/(\\d+).*?(\\d+)/; exit (\\\$1 == \\\$2)' || break; sleep 0.01; i=\\\$((\\\$i-1)); done;\""echo "$(print_time)${1}# killall tcpdump" >> $CONSOLE_LOGeval ssh $SSHCONF root@\$ipv4_${1} "\"killall tcpdump; while true; do killall -q -0 tcpdump || break; sleep 0.01; done;\""eval TDUP_${1}="false"echo "" >> $CONSOLE_LOG}

执行posttest.dat

以下为do-tests文件中执行post-test阶段命令的脚本:

        # execute post-test commands#echo -n "post"echo -e "\nPOST-TEST\n" >> $CONSOLE_LOG 2>&1eval `awk -F "::" '{if ($1 !~ /^#.*/ && $2 != ""){printf("echo \"$(print_time)%s# %s\"; ", $1, $2)printf("ssh \044SSHCONF root@\044ipv4_%s \"%s\"; ", $1, $2)printf("echo;\n")}}' $TESTDIR/posttest.dat` >> $CONSOLE_LOG 2>&1

对于af-alg/alg-camellia测试用例而言,其posttest.dat文件如下。其中的命名正是与pretest.dat文件中的命令相反。

  1 carol::swanctl --terminate --ike home2 carol::systemctl stop strongswan3 moon::systemctl stop strongswan4 moon::iptables-restore < /etc/iptables.flush5 carol::iptables-restore < /etc/iptables.flush

恢复测试环境

将测试开始之前,脚本load-config拷贝到虚拟主机上的配置文件(/etc目录下)备份回来,由脚本restore-defaults实现。

        # copy default host config back if necessary#$DIR/scripts/restore-defaults $testname

测试日志与报告

在测试脚本do-tests执行过程中产生的日志信息都保存在console.log文件中,其保存在目录(/srv/strongswan-testing/testresults/20190917-0126-52/af-alg/alg-camellia/)下,即测试根目录+时间+具体测试用例的目录下。此目录下保存了测试过程中生成的众多文件,其中index.html索引文件,将目录下所有的文件链接起来。

在测试完成之后,作为测试结果备份的文件如下。这些文件都保存在以上提到的具体测试用例的测试结果目录下。首先是ipsec数据库文件

        for host in $DBHOSTSdoeval HOSTLOGIN=root@\$ipv4_${host}scp $SSHCONF $HOSTLOGIN:/etc/db.d/ipsec.sql  $TESTRESULTDIR/${host}.ipsec.sql  > /dev/null 2>&1done

接下来是参与测试的各个主机(IPSECHOSTS)中的配置文件,包括:

  • /etc/strongswan.conf
  • /etc/swanctl/swanctl.conf
  • /etc/ipsec.d/ipsec.sql

以及使用命令保存的状态文件,包括:

  • swanctl.sas
  • swanctl.stats
  • ${host}.ip.policy
  • ${host}.ip.state
  • ${host}.ip.route
  • ${host}.ip.iptables

以及swanctl命令生成的以下文件:

  • ${host}.swanctl.conns
  • ${host}.swanctl.algs
  • ${host}.swanctl.certs
  • ${host}.swanctl.pools
  • ${host}.swanctl.authorities
  • ${host}.swanctl.sas
  • ${host}.swanctl.pols

实现脚本如下:

        for host in $IPSECHOSTSdoeval HOSTLOGIN=root@\$ipv4_${host}scp $SSHCONF $HOSTLOGIN:/etc/strongswan.conf  $TESTRESULTDIR/${host}.strongswan.conf  > /dev/null 2>&1if [  -n "$SWANCTL" ]thenscp $SSHCONF $HOSTLOGIN:/etc/swanctl/swanctl.conf  $TESTRESULTDIR/${host}.swanctl.conf  > /dev/null 2>&1for subsys in conns algs certs pools authorities sas polsdossh $SSHCONF $HOSTLOGIN swanctl --list-$subsys > $TESTRESULTDIR/${host}.swanctl.$subsys 2>/dev/nulldonessh $SSHCONF $HOSTLOGIN swanctl --stats > $TESTRESULTDIR/${host}.swanctl.stats 2>/dev/nullecho "" >> $TESTRESULTDIR/${host}.swanctl.sascat $TESTRESULTDIR/${host}.swanctl.pols >> $TESTRESULTDIR/${host}.swanctl.sascat $TESTRESULTDIR/${host}.swanctl.algs >> $TESTRESULTDIR/${host}.swanctl.statselsefor file in ipsec.conf ipsec.secretsdoscp $SSHCONF $HOSTLOGIN:/etc/$file $TESTRESULTDIR/${host}.$file  > /dev/null 2>&1donefor command in statusall listalldossh $SSHCONF $HOSTLOGIN ipsec $command > $TESTRESULTDIR/${host}.$command 2>/dev/nulldonefiif (! [ -f $TESTRESULTDIR/${host}.ipsec.sql ] ) thenscp $SSHCONF $HOSTLOGIN:/etc/ipsec.d/ipsec.sql $TESTRESULTDIR/${host}.ipsec.sql  > /dev/null 2>&1fissh $SSHCONF $HOSTLOGIN ip -s xfrm policy  > $TESTRESULTDIR/${host}.ip.policy 2>/dev/nullssh $SSHCONF $HOSTLOGIN ip -s xfrm state > $TESTRESULTDIR/${host}.ip.state 2>/dev/nullssh $SSHCONF $HOSTLOGIN $IPROUTE_CMD  > $TESTRESULTDIR/${host}.ip.route 2>/dev/nullssh $SSHCONF $HOSTLOGIN $IPTABLES_CMD  > $TESTRESULTDIR/${host}.iptables 2>/dev/nullssh $SSHCONF $HOSTLOGIN $IPTABLES_SAVE_CMD  > $TESTRESULTDIR/${host}.iptables-save 2>/dev/null

END

SWAN测试执行流程相关推荐

  1. 【测试】测试执行流程

    目录 1. 需求测试 2. 内部发布版本测试(冒烟测试) 3. 系统测试 4. 回归测试 5. 交叉测试 6. 测试报告的输出 1. 需求测试 基于需求的测试方法是基本的测试方法,而需求的质量直接影响 ...

  2. iTunes connect Testflight 2017-04-20改版后的内部测试执行流程

    2017-04-20 iTunes connect改版后,苹果对Testflight进行了很大的改版,众所周知,之前在Testflight里面分为"内部测试员"和"外部测 ...

  3. 【转】Android兼容性测试CTS --环境搭建、测试执行、结果分析

    原文网址:http://www.cnblogs.com/zh-ya-jing/p/4396918.html 为了确保Android应用能够在所有兼容Android的设备上正确运行,并且保持相似的用户体 ...

  4. 动态执行流程分析和性能瓶颈分析的利器——gperftools的Cpu Profiler

    在<动态执行流程分析和性能瓶颈分析的利器--valgrind的callgrind>中,我们领略了valgrind对流程和性能瓶颈分析的强大能力.本文将介绍拥有相似能力的gperftools ...

  5. 使用Caffe进行手写数字识别执行流程解析

    之前在 http://blog.csdn.net/fengbingchun/article/details/50987185 中仿照Caffe中的examples实现对手写数字进行识别,这里详细介绍下 ...

  6. Caffe中对MNIST执行train操作执行流程解析

    之前在 http://blog.csdn.net/fengbingchun/article/details/49849225 中简单介绍过使用Caffe train MNIST的文章,当时只是仿照ca ...

  7. djangorestframework源码分析2:serializer序列化数据的执行流程

    djangorestframework源码分析 本文环境python3.5.2,djangorestframework (3.5.1)系列 djangorestframework源码分析-serial ...

  8. 测试环境搭建流程_案例解析:一个完整的项目测试方案流程,应该是怎么的?...

    作为一名软件测试工程师,为项目制作完成的测试方案并执行,是我们日常工作的重要部分,同时,也是一名合格的软件测试工程师应有的专业素养.那么,很多小白和测试新手肯定要问了:一个完整的项目测试方案流程,应该 ...

  9. Java Web - Struts2基本执行流程

    一 前台测试页面 <%@ page language="java" import="java.util.*" pageEncoding="UTF ...

最新文章

  1. TX Text Control文字处理教程(13)实现拖放操作
  2. Ajax基础知识梳理
  3. ACM-ICPC 2018 沈阳赛区网络预赛 F. Fantastic Graph(有源上下界最大流 模板)
  4. 【已解决】蓝桥杯 2017年C组第五题 杨辉三角(分析与总结)
  5. python题库选择填空_python练习题4.18猴子选大王
  6. LeetCode 980. 不同路径 III(DFS+回溯)
  7. 为什么选择Bootstrap
  8. Java电子书平滑翻页效果_(转载)Android 平滑和立体翻页效果1
  9. 有些投资人从机构出来,自己单干做投资,募资一毛钱都没募到
  10. linux命令grep如何使用,Linux命令之grep命令简单使用
  11. 均方根误差有没有单位_mse均方误差是否有单位
  12. AltiumDesigner绘制PCB(一)
  13. 【OpenCV + Python】时域和频域傅里叶变换
  14. IKexpression解读二
  15. 【多多情报通】看完让人焕然大悟的6种拼多多店铺玩法
  16. linux外网服务器跳转内网服务器实现内网访问(iptables)
  17. /和/*的区别和用法
  18. 解决使用高分辨率笔记本分辨率放大100%以上运行程序界面控件不跟随方大方式qt+gtk+ui
  19. linux如何打印环境变量,在Linux中打印环境变量
  20. 邮件营销 | 精准投放,独立站可提升6倍转化率

热门文章

  1. 高性能计算之九-GPU在ANSYS高性能仿真计算中的应用
  2. 图像矫正--python_OpenCV实现透视变换
  3. html完成公告滚动条,原生js实现公告滚动效果
  4. 我用尽了全力,过着平凡的一生。我的全部努力,不过完成了普通的生活。
  5. 【wpa_supplicant】从 assoc 动作窥伺supplicant与driver的交互(一)
  6. python 可视化 皮肤,Python下载王者荣耀皮肤及个数可视化
  7. Vue 城市联动下拉选择组件实现
  8. 《平凡的世界》《白鹿原》《废都》读后感
  9. 鉴客 Android Intent 用法全面总结
  10. 自学《STM32不完全手册》的笔记三