#This script is using xml to define testcases and testsuite. With this, it is very easier to create new testcases, without need to update the script framework;
#Testunit would be the basic unit in the xml, which corresponding to a proc in tcl script, as the function block, such as make connection, iperf traffic test, set/get MIB etc;
#A testcases.xml is a predefined testcase set, which only need to be updated when a new test scenario is needed;
#Each testcase contains one or more testunit, plus setting and expect result as attr;
#Testsuite xml is user defined collection of testcases and testunits;
#Each xml must have one and only one root tag, and struct like this:
# <root_tag attr1=xxx attr2=yyy ...>
# text
# <sub_tag1 sub_tag1_attr1 ...>
# <sub_tag2 sub_tag2_attr1 ...>
#In topologic, each tag is a node, attr is a list, and sub tags are sub nodes;
#http://wiki.tcl.tk/1740 convert content to xml format, default tag is 'document'
proc asXML {content {tag document}} {
set XML_MAP {
< <
> >
& &
\" "
' '
}
return <$tag>[string map $XML_MAP $content]</$tag>
}
#http://wiki.tcl.tk/3919 A little XML parser
#convert xml to: tag {list: attr} {list: #text {_text_}, subTag1, subTag2 ...}
proc xml2list xml {
# trim only deal with the begin and end of the whole xml
#puts "dbg trim: \[[string trim $xml " \n\t<>"]\]"
regsub -all {>\s*<} [string trim $xml " \n\t<>"] "\} \{" xml
# above regsub replace '>' and '<' with brace if no text between them. It also remove \r \n between '>' and '<'
#puts "dbg after regsub: \[$xml\]"
set xml [string map {> "\} \{#text \{" < "\}\} \{"} $xml]
#puts "dbg after map: \[$xml\]"
set res "" ;# string to collect the result
set stack {} ;# track open tags
set rest {}
foreach item "{$xml}" {
#puts "dbg item => $item"
switch -regexp -- $item {
^# {append res "{$item} " ; #text item, or "{#text [lrange $item 1 end]} ", origin post not work as it said: "{[lrange $item 0 end]} "}
^/ {
regexp {/(.+)} $item -> tagname ;# end tag
set expected [lindex $stack end]
if {$tagname!=$expected} {error "$item != $expected"}
set stack [lrange $stack 0 end-1]
append res "\}\} "
}
/$ { # singleton - start and end in one <> group
regexp {([^ ]+)( (.+))?/$} $item -> tagname - rest
#set rest [lrange [string map {= " "} $rest] 0 end] <= not work if attribute like: attr1="var=x"
regsub -all {=\s*"} $rest " \"" rest
regsub -all {=\s*'} $rest " '" rest
set rest [lrange $rest 0 end]
#with above two lines, attribute has to be single/double quoted
append res "{$tagname [list $rest] {}} "
}
default {
set tagname [lindex $item 0] ;# start tag
#set rest [lrange [string map {= " "} $item] 1 end] <= same as above, not work if attribute contains equal
regsub -all {=\s*"} $item " \"" item
regsub -all {=\s*'} $item " '" item
set rest [lrange $item 1 end]
#with above two lines, attribute value has to be single/double quoted, but the value should not contain such as ="
lappend stack $tagname
append res "\{$tagname [list $rest] \{"
}
}
if {[llength $rest]%2} {error "att's not paired: $rest"}
}
if [llength $stack] {error "unresolved: $stack"}
string map {"\} \}" "\}\}"} [lindex $res 0]
}
proc xputs {msg {log 0}} {
variable xputs_level
set indent ""
if {[info exists xputs_level] && [regexp {^\d+$} $xputs_level]} {set indent [string repeat { } $xputs_level]}
if {$log ne "0"} {send_user "$indent$msg\n"
} else {puts "$indent$msg"}
}
proc print_tree {lxml level} {
#puts "level = $level"
puts "[string repeat { } $level]tag = [lindex $lxml 0]"
puts "[string repeat { } $level]att = [lindex $lxml 1]"
incr level
foreach sitem [lindex $lxml 2] {
print_tree $sitem $level
}
}
#testcases.xml is xml file contains pre-defined testcases. This xml file would be processed to generate an array of testcases definition, no execution here
#testcase name is the index of the array, such as 'TC-SCAN-3-1', and each element of the array defines how to run the testcase, include setting, expect result, and testunit needs to be executed
#the analyze result will be set in variable testcases which is an array;
#Each testcase must have at least one TU, and setting/exp_res passed to the TU/TUs as arguments
#The test xml such as a Test Suite may have one or more testcases. All testcases within one xml node shares all settings, such as repeat counter, timeout setting
#Within one xml node, may have multiple setting, the later setting will be appended, override old value, but no reset. Setting won't be passed to sibling nodes
#Setting would be passed to direct child as reference, where a setting/expect node will update them, and a TC/TU node will take it as input args
#TODO: Currently setting won't be passed cross a node. Setting inherit more than parent? Setting per testcase? Good side: avoid pollution. Bad: cannot share
proc process_testcases_xml {tc_xml_file} {
variable testcases
variable tc_subs ;#for subs defined in testcases_xml
set fp [open $tc_xml_file]
set testcases_xml [read $fp]
close $fp
#puts "Original xml:\n$testcases_xml"
set l_tcs_xml [xml2list [regsub -all {(^|\n)\s*#[^\n]*\n} $testcases_xml {\1}]] ;#remove comment and covert xml to list
set tc_sub_list {}
#puts "tcs xml list: $l_tcs_xml"
#print_tree $l_tcs_xml 0
set ltcs [lindex $l_tcs_xml 2]
foreach tc $ltcs {
#puts "=>$tc"
#=>testcase {name TC-SCAN-3-1} {{setting {Operation Link, Chan 1, Mask 1, Repeat 4} {}} {expect {result success} {}} {run {name TU_scan_test} {}}}
if {[lindex $tc 0] eq "sub"} {
set tc_subs([lindex [lindex $tc 1] 1]) $tc
} else {set testcases([lindex [lindex $tc 1] 1]) [lindex $tc 2]}
}
#puts "\n _dbg_: dump tc_subs:" ; foreach {key value} [array get tc_subs] {puts " $key => $value"}
}
proc get_sub_xlst {lxml sub_name} {
set found false
if {[lindex $lxml 0] eq "sub"} {
array set attrs [lindex $lxml 1]
if {[info exists attrs(name)] && $attrs(name) eq $sub_name} {return $lxml}
}
foreach sitem [lindex $lxml 2] {
set found [get_sub_xlst $sitem $sub_name]
if {$found ne "false"} {return $found}
}
return $found
}
#xlst is a three elments list of a xml node: node_tag, list of attrs, list of sub_node; param level is for debug purpose
#p_setting and p_exp_res is parent setting and exp_res, for current node and current node's sibling nodes
#No inherit of setting and exp_res as that would be messy, error prone and complex. So here allow to pass optional setting as attr of TC.
#Note: there is two ways to run a TC testcase, either use 'run TC-xxx' to start a predefined TC, or have a tag's name started with "TC-"
#Add global AbortTC to abort all testcases whenever needed. Not abort TU as user may still want to do TU_TEST to dump data
set AbortTC no
proc process_xmlnode {xlst level {p_setting ""} {p_exp_res ""}} {
variable testcases
variable tc_subs
variable xputs_level
variable full_tree ;#for processing 'use'
set node [lindex $xlst 0]
set attrs [lindex $xlst 1]
#index 1 in xlst is key-value paired list and 1st pair should be a testunit or testcase name like TU-SETUP-HOSTS or TC-TRAFFIC-10
#other attr could be optional setting of the testcase, or override the TC setting, may also set like expected result
#convert attrs as array as it is key-value pairs
set subnodes [lindex $xlst 2]
set xputs_level $level
upvar $p_setting cur_setting $p_exp_res cur_exp_res
#setting and exp_res for sub nodes
set s_setting ""
set s_exp_res ""
#inherit parent setting/exp_res: 1st, it doesn't work; 2nd, it is a bad approach as explained above.
#set s_setting $p_setting
#set s_exp_res $p_exp_res
#xputs "Level $level node is <$node> with attr list: ($attrs)"
#TODO: decide what to do with the $attrs
if {$node eq "setting"} {
#put setting in upvar variable as it is for parent node, and only need to perform setting in one shot(TU-SETUP-HOSTS?) to make it more effient
set cur_setting [dict merge $cur_setting $attrs]
if {$::LOG_LEVEL>2} {xputs " A setting: $attrs"}
} elseif {$node eq "expect"} {
#put expect to an upvar variable as it is for parent node and it will be checked in testunit of the parent node
set cur_exp_res [dict merge $cur_exp_res $attrs]
if {$::LOG_LEVEL>2} {xputs " An expect: $attrs"}
} elseif {$node eq "exec"} {
#print a message, eval tcl; be careful as support is limited, due to nature of xml and tcl, as quote, slash and many special character will cause issue
#Note $attrs is treated as dict, so duplicate key in $attrs will get lost
#Also, for eval, set variable without uplevel will get lost as this is a recursive call of process_xmlnode
#For exec an external/system command, too many hassle: not sure the cwd, nowhere capture the output; Use eval if needed like this: <exec eval="puts [exec date]" />
if {$::LOG_LEVEL>2} {xputs " An exec request: $attrs"}
dict for {op args} $attrs {
switch -exact -- $op {
"echo" {puts $args}
"eval" {eval $args}
default {puts stderr "Unsupported operation: $op"}
}
}
} elseif {$node eq "run"} {
array set run_setting $attrs
if {[info exists run_setting(name)]} {
if {[regexp {^TC} $run_setting(name)]} {
variable tc_results
variable cur_tc_result
variable tc_idx
variable TC_name
variable f_csv
set TC_name $run_setting(name)
set cur_tc_result ""
#setting for testcase are ignored for now. Should be repeat number, timeout etc.
#upvar $setting cur_setting
puts ""
xputs " <Run testcase $TC_name" 1
if {[info exists f_csv]} {puts $f_csv "Run testcase $TC_name"}
if {$::AbortTC} {
set cur_tc_result "Aborted"
xputs " $cur_tc_result" 1
} else {
#pick the optional setting from TC attr
set s_setting [dict remove $attrs name]
foreach subnode $testcases($TC_name) {process_xmlnode $subnode [expr $level+1] s_setting s_exp_res}
}
dict set tc_results $TC_name $cur_tc_result
incr tc_idx
} elseif {[regexp {^TU} $run_setting(name)]} {
if {$::LOG_LEVEL>1} {xputs " <Run testunit $run_setting(name)" 1}
if {[catch {_$run_setting(name) $cur_setting $cur_exp_res} ret_code]} {
send_user "\n\n**** Exception while running $run_setting(name) ****\n$ret_code\n**** Full error info:\n$::errorInfo\n****\n"
#log_stacktrace ;# too many as this is inside recursive call
}
}
} else {xputs "\t## no-name run with attrs: [array get attrs]"}
} elseif {$node eq "use"} { ;#no attrs, neither sub-nodes expected with use
array set use_setting $attrs
if {[info exists use_setting(name)]} {
set sub_name $use_setting(name)
set use_xlst [get_sub_xlst $full_tree $sub_name] ;#search instead of create sub list on fly, so the sub can be declared after the place invoke it
if {$use_xlst eq "false" && [info exists tc_subs($sub_name)]} {set use_xlst $tc_subs($sub_name)}
if {$use_xlst eq "false"} {
xputs "\t## Not found $sub_name for use. Ignored!"
puts "\n _dbg_: existing tc_subs:" ; foreach {key value} [array get tc_subs] {puts " $key => $value"}
} else {
#puts "*process use: $use_xlst, s=$cur_setting, e=$cur_exp_res"
#process_xmlnode $use_xlst $level cur_setting cur_exp_res
#used node is a container, will ignore the attrs, and directly process subnodes of it
foreach subnode [lindex $use_xlst 2] {process_xmlnode $subnode $level cur_setting cur_exp_res}
}
} else {xputs "\t## Missing tag for use, useless attrs: [array get attrs]"}
} elseif {$node eq "sub"} { ;#sub block which will be invoked by others. so no processing here. Will search the list when needed
} else {
variable TC_name
variable tc_results
variable cur_tc_result
variable tc_idx
set is_TC no
if {[regexp -- {(TC-\S+)} $node -> TC_name]} {
send_user "\n\n<$TC_name>\n"
set is_TC yes
set cur_tc_result ""
}
#only node other than 'setting', 'expect' and 'run' may have sub-nodes
foreach subnode $subnodes {process_xmlnode $subnode [expr $level+1] s_setting s_exp_res}
if {$is_TC} {
dict set tc_results $TC_name $cur_tc_result
incr tc_idx
}
}
}
proc process_test_xml {xml_file {tc_xml_file ""}} {
variable testcases
variable full_tree
variable f_csv
if {[info exists f_csv]} {set create_local_mon_log no
} else {
set create_local_mon_log yes
set f_csv [open monitor.log w]
}
#first, process predefined testcases in $tc_xml_file
if {$tc_xml_file eq ""} {
if {[regexp {([^@]+)@([^@]+)} $xml_file -> xml_file tc_xml_file]==0} {set tc_xml_file $::XML_ROOT_DIR/testcases.xml}
}
process_testcases_xml $tc_xml_file
#foreach {tc_name tc_setting} [array get testcases] {puts "$tc_name => $tc_setting"}
#start processing xml_file
set fp [open $xml_file]
set test_xml [read $fp]
close $fp
#below regsub is for filtering out comment line in XML which is start with '#' as the 1st non-space character of a line, which is not conforming to the XML standard. May be removed in future
regsub -all {(^|\n)\s*#[^\n]*\n} $test_xml {\1} test_xml
set full_tree [xml2list $test_xml]
#print_tree $full_tree 0
variable tc_results
variable tc_idx 0
process_xmlnode $full_tree 0
if {$create_local_mon_log} {close $f_csv}
}
Saturday, April 11, 2020
Tcl parsing xml test description file
Friday, April 10, 2020
Save and Load config file in Perl
sub loadConfigFile {
if ( ( defined $_[0] ) && ( open my $fh, "<$_[0]" ) ) {
local $/ = undef; #set Input record separator for reading file in one shot, set $/ to empty str '' doesn't work
my $str = <$fh>;
close $fh;
$/ = "\n";
my ($meta_build_ref,$cfgref,$pathsref);
eval $str;
#%cfg=%$cfgref; CanNOT do this way, it will create a new %cfg
if ( $log_level >= $LLV_DBG ) { print "meta_build=$meta_build\n";}
#foreach my $key ( keys %cfg ) { $cfg{$key} = $$cfgref{$key}; } =>this will change the array ref
foreach my $key ( keys %cfg ) {
for my $i (0..$#{$cfg{$key}})
{
$cfg{$key}[$i] = $$cfgref{$key}[$i];
}
}
if ( $log_level >= $LLV_DBG ) { print Data::Dumper->Dump( [ \%cfg ] ); }
foreach my $key ( keys %paths ) {
if(defined $$pathsref{$key}) {$paths{$key}=$$pathsref{$key};}
else {$paths{$key}=undef;}
}
if ( $log_level >= $LLV_DBG ) { print Data::Dumper->Dump( [ \%paths ] ); }
}
}
sub loadConfig {
my $filename = $mw->getOpenFile(
-defaultextension => '.cfg',
-filetypes => [ [ 'Config Files', ['.cfg'] ], [ 'All Files', '*', ], ],
-initialdir => $home_dir, #'./'
);
loadConfigFile($filename);
$info="Loaded $filename";
}
sub saveConfig {
my $filename = $mw->getSaveFile(
-defaultextension => '.cfg',
-filetypes => [ [ 'Config Files', ['.cfg'] ], [ 'All Files', '*', ], ],
-initialdir => $home_dir, #'./'
);
if ( ( defined $filename ) && ( open my $fh, ">$filename" ) ) {
print $fh "\$asic='$asic';\n";
print $fh "\$meta_build='$meta_build';\n";
my $str = Data::Dumper->Dump( [ \%cfg ], ['$cfgref'] );
print $fh $str;
if ( $log_level > $LLV_DBG ) { print $str; }
$str = Data::Dumper->Dump( [ \%paths ], ['$pathsref'] );
print $fh $str;
if ( $log_level > $LLV_DBG ) { print $str; }
close $fh;
}
}
Friday, October 18, 2019
计算机是上大学的首选专业
听起来有点太夸张。我上学那会,听说第三次浪潮,产业革命,都是很时髦的概念,经常挂嘴上夸夸其谈,虽然不知其所以然。但现在回头看,社会不没有沿着那些大家推崇的方向发展。单从计算机的发展来说,人工智能(AI),神经元网络,网络计算机在20多年前就被当作计算机的发展前沿,互联网,电子商务,网络交易,虚拟空间,新近的大数据,云存储,云计算,比特币,量子计算机,IoT, AR, VR, 电动汽车自动驾驶等,也都是一会热,一会凉。但总体来说,计算机正在被应用到越来越多的角落,同时创造来更多计算机技术应用的机会和需求,同时也推动了计算机的教学和研究的普及深入。软件技术的发展伴随着电子技术工艺的提高,更高集成化,更快速度,更小功耗,一体化,多功能,新材料,新领域,新着眼点,整体发展多样化伴随着技术进步。
哪些技术会继续成为发展的热点,哪些会成为新的业界宠儿?互联网正把世界的各个角落拉的更近,电子商务交易会更普及并取代更多的实体商店;电子货币会因此取代纸币甚至信用卡。比特币没有银行和国家做后台保障,同时有安全风险,很难成为流通交易的媒介。电子交易极大地推动商业的发展,给更多的人提供更多的机会,更多的竞争,意味着更低价,更高性能,更好更多样的服务;网络交易推动了云服务的发展,而云的发展给小企业商户提供低价优质多样稳定的服务,就连大企业也逐渐从自建网络服务转移到更多依赖专业云供应商的服务,一方面减少开发维护的费用,一方面可以得到更专业更稳定性价比更高的服务,并可以很容易的扩展或缩减服务需求;分工合作就像当年汽车制造采用流水线作业一样,极大提高了效率。从云服务收集到的大数据给云供应商,政府,企业提供导向,使企业政府的决策更精准。伴随语音图像识别和人工智能的发展,人类的生活更方便,像Amazon, Google, Apple等提供的智能家居助手,安保,把我们的生活变得更方便舒适和现代化。未来,你的马桶可能会时刻记录你的体重,血糖,消化代谢系统的状态;你的冰箱会提醒你那些食物要过期了,你还有什么食物储备;你的电子锅会自动为你做出可口的饭菜;你可以随时随地与亲朋好友视频通话;你的垃圾桶每周三早上会自动跑到马路边等着被收集。人脸识别会提供很多便利,乘车,购物,出入,对室外公众场合的安全提供极大的保障。但基于隐私的担心会成为这一技术普及的注意障碍。5G6G的普及,万物互联,给日常生活带来更多便利,提供更多的可能,你的汽车可能会跟临近的汽车协调路线,交通事故将成为历史,你可以随时随地处理公务和私事,办公,求学,就医,讨论,救助。
美国用世界上最好的很多大学,有世界上最先进的技术,有最好的高科技公司,有很多很多聪明能干的工程师企业家。但美国上到总统,下到平民,也有普遍的自高自大,傲慢偏见,唯我独尊。世事无常,所谓的民主会滞肘决策协调,所谓的精英政治只不过少数集团的一己私利。没有长远的历史可借鉴,没有深厚的文化的积垫,没有广泛的外界民众的拥戴,缺少与别国互惠互利的利益共享,繁盛终会走向衰落。
Wednesday, October 2, 2019
用微軟的family setting來控制小孩玩手機和電腦的時間
截屏如下,可以设置和监控Activity, Screen time, App and game limits等。具体参见微软网站:https://account.microsoft.com/family
同时也可以监控Xbox。虽然不是尽善尽美,但是个良好的开始,而且完全free, Google已经开始跟进了。
Tuesday, October 1, 2019
Setup VNC server on Ubuntu
New version of Ubuntu 20.04 and 20.10 has been out for a while. I have been seeing many post about setting up VNC server on Ubuntu, most talking about specifically with TightVNC server and Xfce, without detail explanation of the choice. Xfce is one of Desktop environment for Ubuntu, among other popular environment such as Gnome (default for Ubuntu 20.x), KDE, etc. Basically, Xfce is light weight, so more suitable for VNC remote connection. And for the server, TightVncServer is not the only choice.
- vino, available on Ubuntu by default, but performance is not good
- Vnc4Server is very stable, but might be out-of-date, depends on the desktop environment, such as Gnome or Kde, may need some tweak with x server and authentication configuration;
- TightVnc, UltraVnc, noMachine RealVnc and several other open-source VNC all have some cons and pros, and neither of them is integrated to Ubuntu as good as x11vnc. Some found TightVnc is easy to setup and use, some found noMachine is very powerful. But like TightVnc, it won't allow local user and remote user have the view at same time, so it cannot be used for remote assistant.
- x11vnc is the only one which can be up with the user login screen, so you can type in account credential in the Ubuntu login GUI. One shortage is x11vnc relies on the hardware display. But for headless (no monitor/display adapter), user can set up Xvfb, Xvnc or dummy display. So x11vnc is my favorite one.
This post shows how to setup VNC with TightVNC server + Xfce desktop environment:
https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-vnc-on-ubuntu-18-04
And this combination might be easier to be setup for headless (no monitor connected Linux station), with xserver-xorg-video-dummy package installed. For Xfce, if the Terminal launcher doesn't work, check Settings Manager > Preferred Applications setting, and switch to a different Terminal app.
- make a backup first
- sudo sed -i 's/BIG-REQUESTS/_IG-REQUESTS/' /usr/lib/x86_64-linux-gnu/libxcb.so.1