├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis ├── __init__.py ├── analysis.py ├── connections.py ├── oom.py ├── panic.py ├── ps.py ├── sb_connections.py ├── slab.py └── vmstat.py ├── arguments.py ├── bft ├── bft-node └── Dockerfile ├── boardfarm_config_example.json ├── config.py ├── devices ├── __init__.py ├── base.py ├── board_decider.py ├── common.py ├── configreader.py ├── connection_decider.py ├── debian.py ├── elasticlogger.py ├── error_detect.py ├── local_cmd.py ├── local_serial_connection.py ├── logstash.py ├── marvell.py ├── mysql.py ├── netgear.py ├── openwrt_router.py ├── power.py ├── qcom_akronite_nand.py ├── qcom_akronite_nor.py ├── qcom_arm_base.py ├── qcom_dakota_nand.py ├── qcom_dakota_nor.py ├── qcom_mips.py ├── rpi.py ├── ser2net_connection.py └── ssh_connection.py ├── docs ├── BOARD_FARM.md ├── BoardFarm.png ├── ELK_SETUP.md └── Simple_Board_Farm.jpg ├── html ├── template_results.html └── template_results_basic.html ├── library.py ├── make_human_readable.py ├── requirements.txt ├── results └── README ├── tests ├── __init__.py ├── bridge_mode.py ├── connection_stress.py ├── curl_https.py ├── firewall_on_off.py ├── igmpv3_basic.py ├── interact.py ├── ip_link.py ├── iperf3_test.py ├── iperf_test.py ├── iperf_udp_test.py ├── ipv6_curl.py ├── ipv6_setup.py ├── lib │ ├── __init__.py │ ├── common.py │ ├── installers.py │ ├── streamboost.py │ └── wifi.py ├── linux_boot.py ├── lsmod.py ├── netperf_bidir.py ├── netperf_reverse_test.py ├── netperf_rfc2544.py ├── netperf_stress_test.py ├── netperf_test.py ├── netperf_udp_test.py ├── network_restart.py ├── nmap.py ├── openwrt_version.py ├── opkg.py ├── opkg_conf.py ├── perfperpkt_test.py ├── ping.py ├── ping6.py ├── qdisc.py ├── rootfs_boot.py ├── samba.py ├── ssh_forward_port.py ├── status.py ├── sysupgrade.py ├── ubus.py ├── uci_wireless.py ├── uname.py ├── vmstat.py ├── webbrowse.py ├── webgui.py ├── webui_tests.py ├── wifi_cycle.py ├── wifi_memuse.py ├── wifi_vap_test.py ├── wlan_associate.py ├── wlan_set_ssid.py └── wlan_set_ssid_wpa2psk.py ├── testsuites.cfg └── testsuites.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | 62 | # Ipython Notebook 63 | .ipynb_checkpoints 64 | 65 | # pyenv 66 | .python-version 67 | 68 | # Test results 69 | results/ 70 | 71 | # Text editor temporally files 72 | *.swp 73 | 74 | # ctas 75 | TAGS 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ------------ 3 | 4 | By making a contribution to this project, I certify that: 5 | 6 | 1. The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or 7 | 2. The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or 8 | 3. The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. 9 | 4. I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. 10 | 11 | Using this Process 12 | ------------------ 13 | In short, you need to include a "Signed-off-by" tag in every patch. "Signed-off-by" is a developer's certification that he or she has the right to submit the patch for inclusion into the project. It is an agreement to the Developer's Certificate of Origin (above). Code without a proper signoff cannot be merged into the mainline. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Michael Anderson, Matthew McClintock, Liza Dayoub, Jonathan May 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted (subject to the limitations in the disclaimer below) provided that 7 | the following conditions are met: 8 | * Redistributions of source code must retain the above copyright notice, this list of 9 | conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright notice, this list of 11 | conditions and the following disclaimer in the documentation and/or other materials 12 | provided with the distribution. 13 | * Neither the name of the owners nor the names of contributors may be 14 | used to endorse or promote products derived from this software without specific 15 | prior written permission. 16 | 17 | NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 19 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 20 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 21 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 25 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 26 | OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /analysis/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import os 9 | import glob 10 | # import all analysis classes 11 | analysis_files = glob.glob(os.path.dirname(__file__)+"/*.py") 12 | for x in sorted([os.path.basename(f)[:-3] for f in analysis_files if not "__" in f]): 13 | exec("from %s import *" % x) 14 | -------------------------------------------------------------------------------- /analysis/analysis.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import os 9 | import re 10 | 11 | # no repr 12 | newline = r"\r\n\[[^\]]+\] " 13 | newline_match = r"\r\n\[([^\]]+)\] " 14 | # with repr 15 | newline_re = r"\\r\\n\[[^\]]+\] " 16 | newline_re_match = r"\\r\\n\[([^\]]+)\] " 17 | 18 | def prepare_log(log): 19 | '''Strips some stuff from outside logs so we can parse''' 20 | # TODO: convert other timestamps into seconds since boot 21 | return log 22 | 23 | def split_results(results): 24 | t = [x[0] for x in results] 25 | r = [x[1] for x in results] 26 | 27 | if len(r) == len(t): 28 | return t, r 29 | 30 | # fallback to no timestamps 31 | return None, results 32 | 33 | class Analysis(): 34 | '''Base analysis class, each child class should implement the analyze function''' 35 | def analyze(self, console_log, output_dir): 36 | pass 37 | 38 | def make_graph(self, data, ylabel, fname, ts=None, xlabel="seconds since boot (probably)", output_dir=None): 39 | '''Helper function to make a PNG graph''' 40 | if not output_dir: 41 | return 42 | 43 | import matplotlib as mpl 44 | mpl.use('Agg') 45 | import matplotlib.pyplot as plt 46 | 47 | plt.gca().yaxis.set_major_formatter(mpl.ticker.FormatStrFormatter('%d')) 48 | plt.gca().xaxis.set_major_formatter(mpl.ticker.FormatStrFormatter('%d')) 49 | if ts is None: 50 | plt.plot(data) 51 | else: 52 | plt.plot(ts, data) 53 | plt.ylabel(ylabel) 54 | plt.xlabel(xlabel) 55 | plt.savefig(os.path.join(output_dir, "%s.png" % fname)) 56 | plt.clf() 57 | 58 | # TODO: save simple CSV file? 59 | -------------------------------------------------------------------------------- /analysis/connections.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import analysis 9 | import re 10 | 11 | class ConnectionsAnalysis(analysis.Analysis): 12 | '''Look at logs for number of connections and create graph from results''' 13 | def analyze(self, console_log, output_dir): 14 | regex = "cat /proc/sys/net/netfilter/nf_conntrack_count" \ 15 | + analysis.newline_re_match + "(\d+)" + analysis.newline_re 16 | timestamps, results = analysis.split_results(re.findall(regex, repr(console_log))) 17 | 18 | if len(timestamps) == len(results) and len(results) > 1: 19 | self.make_graph(results, "num connections", "connections", ts=timestamps, output_dir=output_dir) 20 | elif len(results) > 1: 21 | self.make_graph(results, "num connections", "connections", output_dir=output_dir) 22 | -------------------------------------------------------------------------------- /analysis/oom.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import analysis 9 | import re 10 | 11 | class OOMAnalysis(analysis.Analysis): 12 | '''Parse logs for OOM kernel events''' 13 | def analyze(self, console_log, output_dir): 14 | if len(re.findall('Out of memory', console_log)): 15 | print 'ERROR: log had out of memory condition' 16 | -------------------------------------------------------------------------------- /analysis/panic.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import analysis 9 | import re 10 | 11 | class PanicAnalysis(analysis.Analysis): 12 | '''Parse logs for kernel panic events''' 13 | def analyze(self, console_log, output_dir): 14 | if len(re.findall('Kernel panic', console_log)): 15 | print 'ERROR: log had panic' 16 | -------------------------------------------------------------------------------- /analysis/ps.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import analysis 9 | import re 10 | import collections 11 | import os 12 | 13 | class PSAnalysis(analysis.Analysis): 14 | '''Parse top for ps commands, and create graph of process memory usage over time''' 15 | def analyze(self, console_log, output_dir): 16 | regex = "root\\@OpenWrt:[^#]+# ps.*?(?=root@OpenWrt)" 17 | results = re.findall(regex, repr(console_log)) 18 | 19 | # now process each time ps was run 20 | data = collections.defaultdict(list) 21 | timestamps = collections.defaultdict(list) 22 | for ps_dump in results: 23 | for line in ps_dump.split('\\r\\n')[2:]: 24 | line = re.sub('](?=[^\s])', '] ', line) 25 | e = line.split() 26 | if len(e) < 4: 27 | continue 28 | ts = float(e.pop(0).strip('[]')) 29 | pid = e.pop(0) 30 | user = e.pop(0) 31 | mem = e.pop(0) 32 | while e[0] in ['S', 'R', 'SW', 'SW<', 'DW', 'N', '<', 'D', 'Z']: e.pop(0) 33 | cmdline = " ".join(e) 34 | if cmdline[0] == '[' and cmdline[-1] == ']': 35 | cmd = cmdline 36 | else: 37 | cmd = os.path.basename(cmdline.split()[0]) 38 | key = pid + '-' + cmd 39 | data[key].append(mem) 40 | timestamps[key].append(ts) 41 | 42 | for k in data: 43 | if len(data[k]) > 1: 44 | fname = k 45 | for c in r'[]/\;,><&*:%=+@!#^()|?^': 46 | fname = fname.replace(c, '') 47 | self.make_graph(data[k], k, fname, ts=timestamps[k], output_dir=output_dir) 48 | -------------------------------------------------------------------------------- /analysis/sb_connections.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import analysis 9 | import re 10 | 11 | class SbConnectionsAnalysis(analysis.Analysis): 12 | '''Look for streamboost 2.0 number of connections and make graphs''' 13 | def analyze(self, console_log, output_dir): 14 | regex = r"redis-cli -s \$s keys \\'conndb...flow\\' \| wc -l" + \ 15 | analysis.newline_re_match + "(\d+)" + analysis.newline_re 16 | timestamps, results = analysis.split_results(re.findall(regex, repr(console_log))) 17 | 18 | if len(timestamps) == len(results) and len(results) > 1: 19 | self.make_graph(results, "streamboost connections", "sb_connections", ts=timestamps, output_dir=output_dir) 20 | elif len(results) > 1: 21 | self.make_graph(results, "streamboost connections", "sb_connections", output_dir=output_dir) 22 | 23 | regex = "redis-cli -s \$s scard flowdb.flows" + analysis.newline_re_match + \ 24 | "\(integer\) (\d+)" + analysis.newline_re 25 | timestamps, results = analysis.split_results(re.findall(regex, repr(console_log))) 26 | 27 | if len(timestamps) == len(results) and len(results) > 1: 28 | self.make_graph(results, "streamboost flows", "sb_flows", ts=timestamps, output_dir=output_dir) 29 | if len(results) > 1: 30 | self.make_graph(results, "streamboost flows", "sb_flows", output_dir=output_dir) 31 | -------------------------------------------------------------------------------- /analysis/slab.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import analysis 9 | import re 10 | import collections 11 | import os 12 | 13 | class SlabAnalysis(analysis.Analysis): 14 | '''Make graphs for output of /proc/slabinfo over time''' 15 | def analyze(self, console_log, output_dir): 16 | regex = "root\\@OpenWrt:[^#]+# cat /proc/slabinfo.*?(?=root@OpenWrt)" 17 | results = re.findall(regex, repr(console_log)) 18 | 19 | # now process each time ps was run 20 | data = collections.defaultdict(list) 21 | timestamps = collections.defaultdict(list) 22 | for dump in results: 23 | for line in dump.split('\\r\\n')[3:]: 24 | line = re.sub('](?=[^\s])', '] ', line) 25 | e = line.split() 26 | if len(e) < 4: 27 | continue 28 | ts = float(e.pop(0).strip('[]')) 29 | slab_name = e.pop(0) 30 | active_objs = e.pop(0) 31 | num_objs = e.pop(0) 32 | objsize = e.pop(0) 33 | objperslab = e.pop(0) 34 | pagespeslab = e.pop(0) 35 | key = 'slab-' + slab_name 36 | data[key].append(active_objs) 37 | timestamps[key].append(ts) 38 | 39 | for k in data: 40 | if len(data[k]) > 1: 41 | fname = k 42 | for c in r'[]/\;,><&*:%=+@!#^()|?^': 43 | fname = fname.replace(c, '') 44 | self.make_graph(data[k], k, fname, ts=timestamps[k], output_dir=output_dir) 45 | -------------------------------------------------------------------------------- /analysis/vmstat.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import analysis 9 | import re 10 | import collections 11 | 12 | class VmStatAnalysis(analysis.Analysis): 13 | '''Make graphs from /proc/vmstat over time''' 14 | def analyze(self, console_log, output_dir): 15 | results = re.findall(analysis.newline_match + 'nr_(\w+) (\d+)', console_log) 16 | data = collections.defaultdict(list) 17 | timestamps = collections.defaultdict(list) 18 | for t, k, v in results: 19 | data[k].append(int(v)) 20 | timestamps[k].append(float(t)) 21 | 22 | sz = len(data.itervalues().next()) 23 | for k in data: 24 | if len(data[k]) > 1: 25 | sz = min(len(data[k]), sz) 26 | self.make_graph(data[k], k, k, ts=timestamps[k], output_dir=output_dir) 27 | 28 | # not great, just trimming some data to fit but works OK 29 | # for the time being 30 | for k in data: 31 | data[k] = data[k][:sz] 32 | timestamps[k] = timestamps[k][:sz] 33 | 34 | from operator import add 35 | if len(data['slab_unreclaimable']) > 1: 36 | self.make_graph(map(add, data['slab_unreclaimable'], data['active_anon']), 37 | 'slab_unreclaimable + active_anon', 38 | 'slab_unreclaimable+active_anon', 39 | ts=timestamps['slab_unreclaimable'], output_dir=output_dir) 40 | 41 | if len(data['free_pages']) > 1: 42 | self.make_graph(map(add, data['free_pages'], data['inactive_file']), 43 | 'free_pages + inactive_file', 44 | 'free_pages+inactive_file', 45 | ts=timestamps['free_pages'], output_dir=output_dir) 46 | -------------------------------------------------------------------------------- /bft-node/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:8 2 | 3 | RUN echo "root:bigfoot1" | chpasswd 4 | 5 | RUN apt-get update && \ 6 | apt-get install -y --no-install-recommends \ 7 | iproute \ 8 | net-tools \ 9 | openssh-server \ 10 | isc-dhcp-server \ 11 | isc-dhcp-client \ 12 | procps \ 13 | iptables \ 14 | lighttpd \ 15 | tinyproxy \ 16 | curl \ 17 | apache2-utils \ 18 | nmap \ 19 | pppoe \ 20 | tftpd-hpa \ 21 | tcpdump \ 22 | iperf \ 23 | iperf3 \ 24 | netcat 25 | 26 | RUN mkdir /var/run/sshd 27 | RUN sed -i 's/PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config 28 | 29 | EXPOSE 22 30 | -------------------------------------------------------------------------------- /boardfarm_config_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "rpi3-1": { 3 | "board_type": "rpi3", 4 | "conn_cmd": "cu -l /dev/ttyUSB4 -s 115200", 5 | "devices": [ 6 | { 7 | "type": "debian", 8 | "name": "wan", 9 | "pre_cmd_host": "docker build -t bft:node bft-node", 10 | "cmd": "docker run --name wan --privileged -it bft:node /bin/bash", 11 | "post_cmd_host": "sudo ip link set netns $(docker inspect --format '{{.State.Pid}}' wan) dev enx00249b14dc6e", 12 | "post_cmd": "ip link set enx00249b14dc6e name eth1", 13 | "cleanup_cmd": "docker stop wan; docker rm wan", 14 | "color": "cyan", 15 | "options": "tftpd-server" 16 | }, 17 | { 18 | "type": "debian", 19 | "name": "lan", 20 | "pre_cmd_host": "docker build -t bft:node bft-node", 21 | "cmd": "docker run --name lan --privileged -it bft:node /bin/bash", 22 | "post_cmd_host": "sudo ip link set netns $(docker inspect --format '{{.State.Pid}}' lan) dev enx0050b61bfde4", 23 | "post_cmd": "ip link set enx0050b61bfde4 name eth1", 24 | "cleanup_cmd": "docker stop lan; docker rm lan", 25 | "color": "blue" 26 | } 27 | ], 28 | "connection_type": "local_serial", 29 | "notes": "Rpi3 device with docker containers attached to WAN/LAN" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import os 9 | 10 | # Boardfarm configuration describes test stations - see boardfarm doc. 11 | # Can be local or remote file. 12 | boardfarm_config_location = os.environ.get('BFT_CONFIG', 'boardfarm_config_example.json') 13 | 14 | # Test Suite config files. Standard python config file format. 15 | testsuite_config_files = [os.path.join(os.path.dirname(os.path.realpath(__file__)), 'testsuites.cfg'), ] 16 | 17 | # Logstash server - a place to send JSON-format results to 18 | # when finished. Set to None or name:port, e.g. 'logstash.mysite.com:1300' 19 | logging_server = None 20 | 21 | # Elasticsearch server. Data in JSON-format can be directly sent here. 22 | # Set to None or to a valid host, see documentation: 23 | # https://elasticsearch-py.readthedocs.org/en/master/api.html#elasticsearch 24 | elasticsearch_server = os.environ.get('BFT_ELASTICSERVER', None) 25 | 26 | # Code change server like gerrit, github, etc... Used only in display 27 | # of the results html file to list links to code changes tested. 28 | code_change_server = None 29 | -------------------------------------------------------------------------------- /devices/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | This directory contains classes for connecting to and controlling 4 | devices over a network. 5 | 6 | ''' 7 | board = None 8 | lan = None 9 | wan = None 10 | wlan = None 11 | wlan2g = None 12 | wlan5g = None 13 | prompt = None 14 | def initialize_devices(configuration): 15 | # Init random global variables. To Do: clean these. 16 | global power_ip, power_outlet 17 | conn_cmd = configuration.board.get('conn_cmd') 18 | power_ip = configuration.board.get('powerip', None) 19 | power_outlet = configuration.board.get('powerport', None) 20 | # Init devices 21 | global board, lan, wan, wlan, wlan2g, wlan5g, prompt 22 | board = configuration.console 23 | lan = configuration.lan 24 | wan = configuration.wan 25 | wlan = configuration.wlan 26 | wlan2g = configuration.wlan2g 27 | wlan5g = configuration.wlan5g 28 | 29 | for device in configuration.devices: 30 | globals()[device] = getattr(configuration, device) 31 | 32 | board.root_type = None 33 | # Next few lines combines all the prompts into one list of unique prompts. 34 | # It lets test writers use "some_device.expect(prompt)" 35 | prompt = [] 36 | for d in (board, lan, wan, wlan): 37 | prompt += getattr(d, "prompt", []) 38 | prompt = list(set(prompt)) 39 | -------------------------------------------------------------------------------- /devices/base.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import pexpect 9 | from termcolor import colored 10 | from datetime import datetime 11 | import re 12 | 13 | 14 | class BaseDevice(pexpect.spawn): 15 | 16 | prompt = ['root\\@.*:.*#', ] 17 | 18 | def get_interface_ipaddr(self, interface): 19 | self.sendline("\nifconfig %s" % interface) 20 | self.expect('addr:(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}).*(Bcast|P-t-P):', timeout=5) 21 | ipaddr = self.match.group(1) 22 | self.expect(self.prompt) 23 | return ipaddr 24 | 25 | def get_logfile_read(self): 26 | if hasattr(self, "_logfile_read"): 27 | return self._logfile_read 28 | else: 29 | return None 30 | 31 | def expect_prompt(self, timeout=30): 32 | self.expect(self.prompt, timeout=timeout) 33 | 34 | def check_output(self, cmd, timeout=30): 35 | '''Send a string to device, then return the output 36 | between that string and the next prompt.''' 37 | self.sendline("\n" + cmd) 38 | self.expect_exact(cmd, timeout=5) 39 | try: 40 | self.expect(self.prompt, timeout=timeout) 41 | except Exception as e: 42 | self.sendcontrol('c') 43 | raise Exception("Command did not complete within %s seconds. Prompt was not seen." % timeout) 44 | return self.before 45 | 46 | def write(self, string): 47 | self._logfile_read.write(string) 48 | self.log += string 49 | 50 | def set_logfile_read(self, value): 51 | class o_helper(): 52 | def __init__(self, out, color): 53 | self.color = color 54 | self.out = out 55 | self.log = "" 56 | self.start = datetime.now() 57 | def write(self, string): 58 | if self.color is not None: 59 | self.out.write(colored(string, self.color)) 60 | else: 61 | self.out.write(string) 62 | td = datetime.now()-self.start 63 | ts = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 64 | # check for the split case 65 | if len(self.log) > 1 and self.log[-1] == '\r' and string[0] == '\n': 66 | tmp = '\n [%s]' % ts 67 | tmp += string[1:] 68 | string = tmp 69 | self.log += re.sub('\r\n', '\r\n[%s] ' % ts, string) 70 | def flush(self): 71 | self.out.flush() 72 | 73 | if value is not None: 74 | self._logfile_read = o_helper(value, getattr(self, "color", None)) 75 | 76 | def get_log(self): 77 | return self._logfile_read.log 78 | 79 | logfile_read = property(get_logfile_read, set_logfile_read) 80 | log = property(get_log) 81 | 82 | # perf related 83 | def parse_sar_iface_pkts(self, wan, lan): 84 | self.expect('Average.*idle\r\nAverage:\s+all(\s+[0-9]+.[0-9]+){6}\r\n') 85 | idle = float(self.match.group(1)) 86 | self.expect("Average.*rxmcst/s.*\r\n") 87 | 88 | wan_pps = None 89 | client_pps = None 90 | if lan is None: 91 | exp = [wan] 92 | else: 93 | exp = [wan,lan] 94 | 95 | for x in range(0, len(exp)): 96 | i = self.expect(exp) 97 | if i == 0: # parse wan stats 98 | self.expect("(\d+.\d+)\s+(\d+.\d+)") 99 | wan_pps = float(self.match.group(1)) + float(self.match.group(2)) 100 | if i == 1: 101 | self.expect("(\d+.\d+)\s+(\d+.\d+)") 102 | client_pps = float(self.match.group(1)) + float(self.match.group(2)) 103 | 104 | return idle, wan_pps, client_pps 105 | 106 | def check_perf(self): 107 | self.sendline('uname -r') 108 | self.expect('uname -r') 109 | self.expect(self.prompt) 110 | 111 | self.kernel_version = self.before 112 | 113 | self.sendline('\nperf --version') 114 | i = self.expect(['not found', 'perf version']) 115 | self.expect(self.prompt) 116 | 117 | if i == 0: 118 | return False 119 | 120 | return True 121 | 122 | def check_output_perf(self, cmd, events): 123 | perf_args = self.perf_args(events) 124 | 125 | self.sendline("perf stat -a -e %s time %s" % (perf_args, cmd)) 126 | 127 | def parse_perf(self, events): 128 | mapping = self.parse_perf_board() 129 | ret = [] 130 | 131 | for e in mapping: 132 | if e['name'] not in events: 133 | continue 134 | self.expect("(\d+) %s" % e['expect']) 135 | e['value'] = int(self.match.group(1)) 136 | ret.append(e) 137 | 138 | return ret 139 | 140 | # end perf related 141 | -------------------------------------------------------------------------------- /devices/board_decider.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import openwrt_router 9 | import qcom_akronite_nand 10 | import qcom_akronite_nor 11 | import qcom_dakota_nor 12 | import qcom_dakota_nand 13 | import qcom_mips 14 | import marvell 15 | import rpi 16 | 17 | def board(model, **kwargs): 18 | ''' 19 | Depending on the given model, return an object of the appropriate class type. 20 | 21 | Different boards are flashed with different commands, have 22 | different memory addresses, and must be handled differently. 23 | ''' 24 | if model in ("db120", "ap135", "ap143", "ap147", "ap152", "ap151", 25 | "ap151-16M", "ap143", "ap152-8M", "tew-823dru"): 26 | return qcom_mips.QcomMipsRouter(model, **kwargs) 27 | 28 | if model in ("ipq8066", "db149", "ap145", "ap148", "ap148-osprey", 29 | "ap148-beeliner", "ap160-1", "ap160-2", "ap161"): 30 | return qcom_akronite_nand.QcomAkroniteRouterNAND(model, **kwargs) 31 | 32 | if model in ("ap148-nor"): 33 | return qcom_akronite_nor.QcomAkroniteRouterNOR(model, **kwargs) 34 | 35 | if model in ("dk01-nor", "dk04-nor"): 36 | return qcom_dakota_nor.QcomDakotaRouterNOR(model, **kwargs) 37 | 38 | if model in ("dk07-nand", "dk04-nand", "ea8300"): 39 | return qcom_dakota_nand.QcomDakotaRouterNAND(model, **kwargs) 40 | 41 | if model in ("wrt3200acm"): 42 | return marvell.WRT3200ACM(model, **kwargs) 43 | 44 | if model in ("rpi3"): 45 | return rpi.RPI(model, **kwargs) 46 | 47 | # Default for all other models 48 | print("\nWARNING: Unknown board model '%s'." % model) 49 | print("Please check spelling, or write an appropriate class " 50 | "to handle that kind of board.") 51 | return openwrt_router.OpenWrtRouter(model, **kwargs) 52 | -------------------------------------------------------------------------------- /devices/common.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 8 | 9 | import binascii 10 | import os 11 | import pexpect 12 | import sys 13 | import urllib2 14 | 15 | import termcolor 16 | 17 | def get_file_magic(fname, num_bytes=4): 18 | '''Return the first few bytes from a file to determine the type.''' 19 | if fname.startswith("http://") or fname.startswith("https://"): 20 | rng = 'bytes=0-%s' % (num_bytes-1) 21 | req = urllib2.Request(fname, headers={'Range': rng}) 22 | data = urllib2.urlopen(req).read() 23 | else: 24 | f = open(fname, 'rb') 25 | data = f.read(num_bytes) 26 | f.close() 27 | return binascii.hexlify(data) 28 | 29 | def copy_file_to_server(cmd, password): 30 | '''Requires a command like ssh/scp to transfer a file, and a password. 31 | Run the command and enter the password if asked for one.''' 32 | for attempt in range(5): 33 | try: 34 | print_bold(cmd) 35 | p = pexpect.spawn(command='/bin/bash', args=['-c', cmd], timeout=120) 36 | p.logfile_read = sys.stdout 37 | 38 | i = p.expect(["yes/no", "password:", "/tftpboot/.*"]) 39 | if i == 0: 40 | p.sendline("yes") 41 | i = p.expect(["not used", "password:", "/tftpboot/.*"], timeout=45) 42 | 43 | if i == 1: 44 | p.sendline("%s" % password) 45 | p.expect("/tftpboot/.*", timeout=120) 46 | 47 | fname = p.match.group(0).strip() 48 | print_bold("\nfile: %s" % fname) 49 | except Exception as e: 50 | print_bold(e) 51 | print_bold("tried to copy file to server and failed!") 52 | else: 53 | return fname[10:] 54 | 55 | print_bold("Unable to copy file to server, exiting") 56 | raise Exception("Unable to copy file to server") 57 | 58 | def download_from_web(url, server, username, password, port): 59 | try: 60 | urllib2.urlopen(url) 61 | except urllib2.HTTPError as e: 62 | print_bold("HTTP url %s returned %s, exiting" % (url, e.code)) 63 | sys.exit(10) 64 | except urllib2.URLError as e: 65 | print_bold("HTTP url %s returned %s, exiting" % (url, e.args)) 66 | sys.exit(11) 67 | cmd = "curl -L -k '%s' 2>/dev/null | ssh -p %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -x %s@%s \"tmpfile=\`mktemp /tftpboot/tmp/XXXXX\`; cat - > \$tmpfile; chmod a+rw \$tmpfile; echo \$tmpfile\"" % (url, port, username, server) 68 | return copy_file_to_server(cmd, password) 69 | 70 | def scp_to_tftp_server(fname, server, username, password, port): 71 | # local file verify it exists first 72 | if not os.path.isfile(fname): 73 | print_bold("File passed as parameter does not exist! Failing!\n") 74 | sys.exit(10) 75 | 76 | cmd = "cat %s | ssh -p %s -x %s@%s \"tmpfile=\`mktemp /tftpboot/tmp/XXXXX\`; cat - > \$tmpfile; chmod a+rw \$tmpfile; echo \$tmpfile\"" % (fname, port, username, server) 77 | return copy_file_to_server(cmd, password) 78 | 79 | def print_bold(msg): 80 | termcolor.cprint(msg, None, attrs=['bold']) 81 | -------------------------------------------------------------------------------- /devices/configreader.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | #!/usr/bin/env python 8 | 9 | import re 10 | 11 | try: 12 | from urllib.request import urlopen 13 | except: 14 | from urllib2 import urlopen 15 | 16 | class TestsuiteConfigReader(object): 17 | ''' 18 | Read config file like: 19 | 20 | [testsuiteA] 21 | test1 22 | test2 23 | test1 24 | test2 25 | [testsuiteB] 26 | test7 27 | test2 28 | [testsuiteC] 29 | @testsuiteB 30 | test3 31 | 32 | And from that, create dictionary like: 33 | 34 | {'testsuiteA' : [test1, test2, test1, test2, ...] 35 | 'testsuiteB' : [test7, test2, ...] 36 | 'testsuiteC' : [test7, test2, test3, ...] 37 | } 38 | ''' 39 | 40 | def __init__(self): 41 | self.section = {} 42 | 43 | def read(self, filenames): 44 | for f in filenames: 45 | try: 46 | self.read_config(f) 47 | except Exception as e: 48 | print(e) 49 | continue 50 | 51 | def read_config(self, fname): 52 | ''' 53 | Read local or remote (http) config file and parse into a dictionary. 54 | ''' 55 | try: 56 | if fname.startswith("http"): 57 | s_config = urlopen(fname).read() 58 | else: 59 | s_config = open(fname, 'r').read() 60 | except Exception as e: 61 | print(e) 62 | raise Exception("Warning: Unable to read/access %s" % fname) 63 | current_section = None 64 | for i, line in enumerate(s_config.split('\n')): 65 | try: 66 | if line == '' or re.match('^\s+', line) or line.startswith('#'): 67 | continue 68 | if '[' in line: 69 | current_section = re.search('\[(.*)\]', line).group(1) 70 | if current_section not in self.section: 71 | self.section[current_section] = [] 72 | if '@' in line: 73 | ref_section = re.search('@(.*)', line).group(1) 74 | if ref_section in self.section: 75 | self.section[current_section] = self.section[current_section] + self.section[ref_section] 76 | elif re.match('\w+', line): 77 | if current_section: 78 | self.section[current_section].append(line) 79 | except Exception as e: 80 | print(e) 81 | print("Error line %s of %s" % (i+1, fname)) 82 | continue 83 | 84 | def __str__(self): 85 | result = [] 86 | for name in sorted(self.section): 87 | result.append('* %s' % name) 88 | for i, x in enumerate(self.section[name]): 89 | result.append(' %2s %s' % (i+1, x)) 90 | return "\n".join(result) 91 | 92 | if __name__ == '__main__': 93 | import os 94 | filenames = [os.path.join(os.path.dirname(os.path.realpath(__file__)), '../testsuites.cfg'), 95 | ] 96 | t = TestsuiteConfigReader() 97 | t.read(filenames) 98 | print(t) 99 | -------------------------------------------------------------------------------- /devices/connection_decider.py: -------------------------------------------------------------------------------- 1 | import ser2net_connection 2 | import local_serial_connection 3 | import ssh_connection 4 | import local_cmd 5 | 6 | def connection(conn_type, device, **kwargs): 7 | ''' 8 | Depending on the given connection type return an object of the appropriate class type. 9 | ''' 10 | if conn_type in ("ser2net"): 11 | return ser2net_connection.Ser2NetConnection(device=device,**kwargs) 12 | 13 | if conn_type in ("local_serial"): 14 | return local_serial_connection.LocalSerialConnection(device=device,**kwargs) 15 | 16 | if conn_type in ("ssh"): 17 | return ssh_connection.SshConnection(device=device, **kwargs) 18 | 19 | if conn_type in ("local_cmd"): 20 | return local_cmd.LocalCmd(device=device, **kwargs) 21 | 22 | # Default for all other models 23 | print("\nWARNING: Unknown connection type '%s'." % type) 24 | print("Please check spelling, or write an appropriate class " 25 | "to handle that kind of board.") 26 | return ser2net_connection.Ser2NetConnection(**kwargs) 27 | -------------------------------------------------------------------------------- /devices/elasticlogger.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | #!/usr/bin/env python 8 | 9 | import datetime 10 | import os 11 | import socket 12 | import sys 13 | 14 | try: 15 | import elasticsearch 16 | except Exception as e: 17 | print(e) 18 | print("Please install needed module:\n" 19 | " sudo pip install -U elasticsearch") 20 | sys.exit(1) 21 | 22 | class ElasticsearchLogger(object): 23 | ''' 24 | Write data directly to an elasticsearch cluster. 25 | ''' 26 | 27 | def __init__(self, server, index='boardfarm', doc_type='bft_run'): 28 | self.server = server 29 | self.index = index + "-" + datetime.datetime.utcnow().strftime("%Y.%m.%d") 30 | self.doc_type = doc_type 31 | # Connect to server 32 | self.es = elasticsearch.Elasticsearch([server]) 33 | # Set default data 34 | username = os.environ.get('BUILD_USER_ID', None) 35 | if username is None: 36 | username = os.environ.get('USER', '') 37 | self.default_data = { 38 | 'hostname': socket.gethostname(), 39 | 'user': username, 40 | 'build_url': os.environ.get('BUILD_URL', 'None'), 41 | 'change_list': os.environ.get('change_list', 'None'), 42 | 'apss': os.environ.get('apss', 'None').split('-')[0], 43 | 'manifest': os.environ.get('manifest', 'None'), 44 | } 45 | 46 | def log(self, data, debug=False): 47 | # Put in default data 48 | self.default_data['@timestamp'] = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z") 49 | data.update(self.default_data) 50 | result = self.es.index(index=self.index, doc_type=self.doc_type, body=data) 51 | if result and 'created' in result and result['created'] == True: 52 | doc_url = "%s%s/%s/%s" % (self.server, self.index, self.doc_type, result['_id']) 53 | print("Elasticsearch: Data stored at %s" % (doc_url)) 54 | else: 55 | print(result) 56 | raise Exception('Elasticsearch: problem storing data.') 57 | if debug: 58 | print(data) 59 | -------------------------------------------------------------------------------- /devices/error_detect.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import pexpect 9 | import common 10 | import re 11 | import inspect 12 | import os 13 | 14 | # Add this to your env if you need to disable this for some reason 15 | BFT_DISABLE_ERROR_DETECT = "BFT_DISABLE_ERROR_DETECT" in os.environ 16 | 17 | def detect_kernel_panic(console, s): 18 | if re.findall("Kernel panic - not syncing", s): 19 | console.close() 20 | 21 | raise Exception('Kernel panic detected') 22 | 23 | def detect_crashdump_error(console, s): 24 | if re.findall("Crashdump magic found", s): 25 | common.print_bold("Crashdump magic found, trying to save data..."); 26 | 27 | console.sendcontrol('c') 28 | console.sendcontrol('c') 29 | console.sendcontrol('c') 30 | console.expect('') 31 | console.expect(console.uprompt) 32 | console.setup_uboot_network() 33 | console.sendline("dumpipq_data") 34 | 35 | tftp_progress = "#" * 30 36 | tftp_start = "TFTP to server" 37 | tftp_done = "Bytes transferred" 38 | tftp_expect = [tftp_progress, tftp_start, tftp_done] 39 | 40 | i = -1 41 | try: 42 | # this waits until we get the reseting message which means 43 | # we are done 44 | while i < 3: 45 | i = console.expect(tftp_expect + 46 | ["Resetting with watch dog!"] + console.uprompt) 47 | except: 48 | common.print_bold("Crashdump upload failed") 49 | else: 50 | common.print_bold("Crashdump upload succeeded") 51 | 52 | # TODO: actually parse data too? 53 | raise Exception('Crashdump detected') 54 | 55 | def detect_fatal_error(console): 56 | if BFT_DISABLE_ERROR_DETECT: 57 | return 58 | 59 | s = "" 60 | 61 | if isinstance(console.before, str): 62 | s += console.before 63 | if isinstance(console.after, str): 64 | s += console.after 65 | 66 | detect_crashdump_error(console, s) 67 | #detect_kernel_panic(console, s) 68 | 69 | def caller_file_line(i): 70 | caller = inspect.stack()[i] # caller of spawn or pexpect 71 | frame = caller[0] 72 | info = inspect.getframeinfo(frame) 73 | 74 | # readline calls expect 75 | if info.function == "readline": 76 | # note: we are calling ourselves, so we have to add more than 1 here 77 | return caller_file_line(i+2) 78 | return "%s: %s(): line %s" % (info.filename, info.function, info.lineno) 79 | 80 | 81 | -------------------------------------------------------------------------------- /devices/local_cmd.py: -------------------------------------------------------------------------------- 1 | import pexpect 2 | 3 | class LocalCmd(): 4 | ''' 5 | Set connection_type to local_cmd, ignores all output for now 6 | ''' 7 | def __init__(self, device=None, conn_cmd=None, **kwargs): 8 | self.device = device 9 | self.conn_cmd = conn_cmd 10 | 11 | def connect(self): 12 | pexpect.spawn.__init__(self.device, 13 | command='/bin/bash', 14 | args=['-c', self.conn_cmd]) 15 | 16 | def close(): 17 | self.device.sendcontrol('c') 18 | -------------------------------------------------------------------------------- /devices/local_serial_connection.py: -------------------------------------------------------------------------------- 1 | import pexpect 2 | 3 | class LocalSerialConnection(): 4 | ''' 5 | To use, set conn_cmd in your json to "cu -s -l " 6 | and set connection_type to "local_serial" 7 | 8 | ''' 9 | def __init__(self, device=None, conn_cmd=None, **kwargs): 10 | self.device = device 11 | self.conn_cmd = conn_cmd 12 | 13 | def connect(self): 14 | pexpect.spawn.__init__(self.device, 15 | command='/bin/bash', 16 | args=['-c', self.conn_cmd]) 17 | try: 18 | result = self.device.expect([".*Connected.*", "----------------------------------------------------"]) 19 | except pexpect.EOF as e: 20 | raise Exception("Board is in use (connection refused).") 21 | 22 | def close(): 23 | self.device.sendline("~.") 24 | -------------------------------------------------------------------------------- /devices/logstash.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | #!/usr/bin/env python 8 | 9 | import json 10 | import os 11 | import socket 12 | 13 | class RemoteLogger(object): 14 | ''' 15 | Write data to remote logging server, in thise case, Logstash. 16 | ''' 17 | 18 | def __init__(self, server, subtype='demo'): 19 | '''Logging server requires some default data for easy searching.''' 20 | username = os.environ.get('BUILD_USER_ID', None) 21 | if username is None: 22 | username = os.environ.get('USER', '') 23 | self.default_data = { 24 | 'type': 'qcatest', 25 | 'subtype': subtype, 26 | 'hostname': socket.gethostname(), 27 | 'user': username, 28 | 'build_url': os.environ.get('BUILD_URL', 'None'), 29 | 'change_list': os.environ.get('change_list', 'None'), 30 | 'apss': os.environ.get('apss', 'None').split('-')[0], 31 | 'manifest': os.environ.get('manifest', 'None'), 32 | } 33 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 34 | name, port = server.split(':') 35 | self.logserver_ip = socket.gethostbyname(name) 36 | self.logserver_port = int(port) 37 | 38 | def log(self, data, debug=False): 39 | '''data must be a simple dictionary.''' 40 | # Put in default data 41 | data.update(self.default_data) 42 | s = json.dumps(data) 43 | self.sock.sendto(s, (self.logserver_ip, self.logserver_port)) 44 | print("Logstash: %s bytes of data sent to %s:%s." % (len(s), self.logserver_ip, self.logserver_port)) 45 | if len(s) > 8192: 46 | print("Logstash: WARNING, Data size too large. May not have logged result.") 47 | if debug: 48 | print(data) 49 | -------------------------------------------------------------------------------- /devices/marvell.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import common 9 | import openwrt_router 10 | import pexpect 11 | import time 12 | 13 | class WRT3200ACM(openwrt_router.OpenWrtRouter): 14 | ''' 15 | Marvell board 16 | ''' 17 | 18 | prompt = ['root\\@.*:.*#', ] 19 | uprompt = ['Venom>>'] 20 | uboot_eth = "egiga1" 21 | wan_iface = "wan" 22 | 23 | def reset(self, break_into_uboot=False): 24 | if not break_into_uboot: 25 | self.power.reset() 26 | else: 27 | self.wait_for_boot() 28 | 29 | def wait_for_boot(self): 30 | '''Power-cycle this device.''' 31 | self.power.reset() 32 | 33 | self.expect_exact('General initialization - Version: 1.0.0') 34 | for not_used in range(10): 35 | self.expect(pexpect.TIMEOUT, timeout=0.1) 36 | self.sendline('echo FOO') 37 | if 0 != self.expect([pexpect.TIMEOUT] + ['echo FOO'], timeout=0.1): 38 | break 39 | if 0 != self.expect([pexpect.TIMEOUT] + ['FOO'], timeout=0.1): 40 | break 41 | if 0 != self.expect([pexpect.TIMEOUT] + self.uprompt, timeout=0.1): 42 | break 43 | time.sleep(1) 44 | 45 | def wait_for_linux(self): 46 | self.wait_for_boot() 47 | self.sendline("boot") 48 | super(WRT3200ACM, self).wait_for_linux() 49 | 50 | def flash_linux(self, KERNEL): 51 | common.print_bold("\n===== Flashing linux =====\n") 52 | filename = self.prepare_file(KERNEL) 53 | self.sendline('setenv firmwareName %s' % filename) 54 | self.expect(self.uprompt) 55 | self.sendline('run update_both_images') 56 | self.expect(self.uprompt, timeout=90) 57 | 58 | def boot_linux(self, rootfs=None): 59 | self.sendline('boot') 60 | -------------------------------------------------------------------------------- /devices/mysql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2015 3 | # 4 | # All rights reserved. 5 | # 6 | # This file is distributed under the Clear BSD license. 7 | # The full text can be found in LICENSE in the root directory. 8 | 9 | import sys 10 | 11 | class MySqlReporter(): 12 | ''' 13 | If results are archived somewhere (e.g. a Jenkins server, or other 14 | build server), the MySqlReporter class can be used to insert the 15 | location of the results to a MySQL database. 16 | 17 | Example credential file: 18 | host="myserver.something.com" 19 | port=3349 20 | user="username" 21 | passwd="password" 22 | db="database_name" 23 | ''' 24 | 25 | def __init__(self, 26 | credential_dir='/local/mnt/workspace/', 27 | credential_file='findit_db'): 28 | try: 29 | import MySQLdb 30 | except Exception as e: 31 | print(e) 32 | print("WARNING: Unable to import MySQLdb.") 33 | print("Perhaps install it: sudo apt-get install python-mysqldb") 34 | raise Exception 35 | try: 36 | sys.path.insert(0, credential_dir) 37 | self.credentials = __import__(credential_file) 38 | except Exception as e: 39 | print(e) 40 | print("WARNING: Unable to import mysql credentials:\n" 41 | "%s" % (credential_dir + credential_file)) 42 | raise Exception 43 | self.db = MySQLdb.connect(host=self.credentials.host, 44 | port=self.credentials.port, 45 | user=self.credentials.user, 46 | passwd=self.credentials.passwd, 47 | db=self.credentials.db) 48 | self.cur = self.db.cursor() 49 | 50 | def insert_data(self, build_id, url, jobname): 51 | ''' 52 | Given a build ID (e.g. APSS.LN.0.0.1-00047-S-1) and a 53 | url, insert these into the database. 54 | ''' 55 | cmd = ("INSERT INTO TestResults " 56 | "(TestResultsBuildID, TestResultsJobName, TestResultsLink) " 57 | "VALUES (%(BuildID)s, %(JobName)s, %(ResultsLink)s)") 58 | data = { 59 | 'BuildID': build_id, 60 | 'JobName': jobname, 61 | 'ResultsLink': url 62 | } 63 | try: 64 | print('Insert into myqsl %s:%s: %s' % (self.credentials.host, 65 | self.credentials.port, 66 | data)) 67 | self.cur.execute(cmd, data) 68 | self.db.commit() 69 | except Exception as e: 70 | print(e) 71 | print("WARNING: Unable to insert data into database.") 72 | 73 | if __name__ == '__main__': 74 | reporter = MySqlReporter() 75 | reporter.insert_data('test', 'something.com/openwrt/results/results.html') 76 | -------------------------------------------------------------------------------- /devices/netgear.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | #!/usr/bin/env python 8 | 9 | import pexpect 10 | import sys 11 | import base 12 | 13 | # Netgear Switch Prompt 14 | prompt = "\(M4100-50G\) " 15 | 16 | class NetgearM4100(base.BaseDevice): 17 | ''' 18 | A netgear switch allows for changing connections by modifying 19 | VLANs on ports. 20 | ''' 21 | 22 | def __init__(self, 23 | conn_cmd, 24 | username='admin', 25 | password='bigfoot1'): 26 | pexpect.spawn.__init__(self, '/bin/bash', args=['-c', conn_cmd]) 27 | self.logfile_read = sys.stdout 28 | self.username = username 29 | self.password = password 30 | self.prompt = prompt 31 | self.connect() 32 | 33 | def connect(self): 34 | self.sendline("\n") 35 | i = self.expect(["User:", prompt], timeout=20) 36 | if i == 0: 37 | self.sendline(self.username) 38 | self.expect("Password:") 39 | self.sendline(self.password) 40 | self.expect(prompt) 41 | 42 | def disconnect(self): 43 | # Leave config mode 44 | self.sendline("exit") 45 | self.expect(prompt) 46 | # Leave privileged mode 47 | self.sendline("exit") 48 | self.expect(prompt) 49 | # Quit 50 | self.sendline("quit") 51 | self.expect('User:') 52 | self.close() 53 | 54 | def change_port_vlan(self, port, vlan): 55 | # Enter privileged mode 56 | self.sendline("enable") 57 | i = self.expect([prompt, "Password:"]) 58 | if i == 1: 59 | self.sendline(self.password) 60 | self.expect(prompt) 61 | # Enter config mode 62 | self.sendline("configure") 63 | self.expect(prompt) 64 | # Enter interface config mode 65 | port_name = "0/%01d" % port 66 | self.sendline("interface %s" % port_name) 67 | self.expect(prompt) 68 | # Remove previous VLAN 69 | self.sendline("no vlan pvid") 70 | self.expect(prompt) 71 | self.sendline("vlan participation exclude 3-1024") 72 | self.expect(prompt) 73 | # Include new VLAN 74 | self.sendline("vlan pvid %s" % vlan) 75 | self.expect(prompt) 76 | self.sendline("vlan participation include %s" % vlan) 77 | self.expect(prompt) 78 | # Leave interface config mode 79 | self.sendline("exit") 80 | self.expect(prompt) 81 | 82 | def setup_standard_vlans(self, min_port=1, max_port=49): 83 | ''' 84 | Create enough VLANs, then put ports on VLANS such that: 85 | port 1 & 2 are on VLAN 3 86 | port 3 & 4 are on VLAN 4 87 | etc... 88 | Also remove all ports from VLAN 1 (default setting). 89 | ''' 90 | # Enter privileged mode 91 | self.sendline("enable") 92 | i = self.expect([prompt, "Password:"]) 93 | if i == 1: 94 | self.sendline("password") 95 | self.expect(prompt) 96 | # Create all VLANS 97 | self.sendline("vlan database") 98 | self.expect(prompt) 99 | self.sendline("vlan 3-50") 100 | self.expect(prompt) 101 | self.sendline("exit") 102 | self.expect(prompt) 103 | # Enter config mode 104 | self.sendline("configure") 105 | self.expect(prompt) 106 | # Remove all interfaces from VLAN 1 (default setting) 107 | self.sendline("interface 0/1-0/48") 108 | self.expect(prompt) 109 | self.sendline("vlan participation exclude 1") 110 | self.expect(prompt) 111 | self.sendline("exit") 112 | self.expect(prompt) 113 | # Loop over all interfaces 114 | pvid = 3 # initial offset of 3 due to netgear BS 115 | for i in range(min_port, max_port, 2): 116 | low = i 117 | high = i + 1 118 | # configure interfaces 119 | self.sendline("interface 0/%01d-0/%01d" % (low, high)) 120 | self.expect(prompt) 121 | self.sendline("vlan pvid %s" % pvid) 122 | self.expect(prompt) 123 | self.sendline("vlan participation include %s" % pvid) 124 | self.expect(prompt) 125 | # Leave interface configuration 126 | self.sendline("exit") 127 | self.expect(prompt) 128 | pvid += 1 129 | 130 | def print_vlans(self): 131 | ''' 132 | Query each port on switch to see connected mac addresses. 133 | Print connection table in the end. 134 | ''' 135 | vlan_macs = {} 136 | # Enter privileged mode 137 | self.sendline("enable") 138 | i = self.expect([prompt, "Password:"]) 139 | if i == 1: 140 | self.sendline("password") 141 | self.expect(prompt) 142 | # Check each port 143 | for p in range(1, 48): 144 | port_name = "0/%01d" % p 145 | self.sendline('show mac-addr-table interface %s' % port_name) 146 | tmp = self.expect(['--More--', prompt]) 147 | if tmp == 0: 148 | self.sendline() 149 | result = self.before.split('\n') 150 | for line in result: 151 | if ':' in line: 152 | mac, vlan, _ = line.split() 153 | vlan = int(vlan) 154 | if vlan not in vlan_macs: 155 | vlan_macs[vlan] = [] 156 | vlan_macs[vlan].append(mac) 157 | #print vlan_macs 158 | print("\n\n") 159 | print("VLAN Devices") 160 | print("---- -----------------") 161 | for vlan in sorted(vlan_macs): 162 | #devices = [mac_to_name(x) for x in vlan_macs[vlan]] 163 | devices = [x for x in vlan_macs[vlan]] 164 | print("%4s %s" % (vlan, " <-> ".join(devices))) 165 | 166 | if __name__ == '__main__': 167 | switch = NetgearM4100(conn_cmd='telnet 10.0.0.64 6031', 168 | username='admin', 169 | password='password' 170 | ) 171 | switch.print_vlans() 172 | switch.disconnect() 173 | -------------------------------------------------------------------------------- /devices/qcom_akronite_nand.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import common 9 | import qcom_arm_base 10 | 11 | 12 | class QcomAkroniteRouterNAND(qcom_arm_base.QcomArmBase): 13 | ''' 14 | Board with an Akronite processor. 15 | ''' 16 | 17 | machid_table = {"db149": "125b", "ap145": "12ca", 18 | "ap148": "1260", "ap148-beeliner": "1260", 19 | "ap148-osprey": "1260", "ap160-1": "136b", 20 | "ap160-2": "136b", "ap161": "136c", 21 | "dk04": "8010001"} 22 | uboot_ddr_addr = "0x42000000" 23 | 24 | def __init__(self, *args, **kwargs): 25 | super(QcomAkroniteRouterNAND, self).__init__(*args, **kwargs) 26 | if self.model in self.machid_table: 27 | self.machid = self.machid_table[self.model] 28 | else: 29 | raise Exception("Unknown machid for %s, please add to table") 30 | 31 | def flash_uboot(self, uboot): 32 | '''Flash Universal Bootloader image.''' 33 | common.print_bold("\n===== Flashing u-boot =====\n") 34 | filename = self.prepare_file(uboot) 35 | size = self.tftp_get_file_uboot(self.uboot_ddr_addr, filename) 36 | self.sendline('ipq_nand sbl') 37 | self.expect(self.uprompt) 38 | self.nand_flash_bin(self.uboot_addr, self.uboot_size, self.uboot_ddr_addr) 39 | self.reset() 40 | self.wait_for_boot() 41 | self.setup_uboot_network() 42 | 43 | def flash_rootfs(self, ROOTFS): 44 | common.print_bold("\n===== Flashing rootfs =====\n") 45 | filename = self.prepare_file(ROOTFS) 46 | 47 | size = self.tftp_get_file_uboot(self.uboot_ddr_addr, filename) 48 | self.nand_flash_bin(self.rootfs_addr, self.rootfs_size, self.uboot_ddr_addr) 49 | 50 | def flash_linux(self, KERNEL): 51 | common.print_bold("\n===== Flashing linux =====\n") 52 | filename = self.prepare_file(KERNEL) 53 | 54 | raise Exception("Kernel is in UBI rootfs, not separate") 55 | 56 | def boot_linux(self, rootfs=None): 57 | common.print_bold("\n===== Booting linux for %s on %s =====" % (self.model, self.root_type)) 58 | self.reset() 59 | self.wait_for_boot() 60 | 61 | self.sendline("setenv bootcmd bootipq") 62 | self.expect(self.uprompt) 63 | self.sendline("saveenv") 64 | self.expect(self.uprompt) 65 | self.sendline("print") 66 | self.expect(self.uprompt) 67 | self.sendline('run bootcmd') 68 | # if run isn't support, we just reset u-boot and 69 | # let the bootcmd run that way 70 | try: 71 | self.expect('Unknown command', timeout=5) 72 | except: 73 | pass 74 | else: 75 | self.sendline('reset') 76 | -------------------------------------------------------------------------------- /devices/qcom_akronite_nor.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import common 9 | import qcom_arm_base 10 | 11 | class QcomAkroniteRouterNOR(qcom_arm_base.QcomArmBase): 12 | ''' 13 | Board with an Akronite processor. 14 | ''' 15 | 16 | def __init__(self, *args, **kwargs): 17 | super(QcomAkroniteRouterNOR, self).__init__(*args, **kwargs) 18 | self.uboot_ddr_addr = "0x42000000" 19 | machid_table = {"ap148-nor": "1260"} 20 | if self.model in machid_table: 21 | self.machid = machid_table[self.model] 22 | else: 23 | raise Exception("Unknown machid for %s, please add to table") 24 | 25 | def flash_rootfs(self, ROOTFS): 26 | common.print_bold("\n===== Flashing rootfs =====\n") 27 | filename = self.prepare_file(ROOTFS) 28 | 29 | size = self.tftp_get_file_uboot(self.uboot_ddr_addr, filename) 30 | self.spi_flash_bin("0x006b0000", size, self.uboot_ddr_addr, "0x1920000") 31 | 32 | def flash_linux(self, KERNEL): 33 | common.print_bold("\n===== Flashing linux =====\n") 34 | filename = self.prepare_file(KERNEL) 35 | 36 | size = self.tftp_get_file_uboot(self.uboot_ddr_addr, filename) 37 | self.spi_flash_bin("0x0062b0000", size, self.uboot_ddr_addr, "0x400000") 38 | 39 | def boot_linux(self, rootfs=None): 40 | common.print_bold("\n===== Booting linux for %s on %s =====" % (self.model, self.root_type)) 41 | self.reset() 42 | self.wait_for_boot() 43 | 44 | self.sendline("set bootargs 'console=ttyMSM0,115200'") 45 | self.expect(self.uprompt) 46 | self.sendline("set fsbootargs 'rootfstype=squashfs,jffs2'") 47 | self.expect(self.uprompt) 48 | self.sendline('set bootcmd bootipq') 49 | self.expect(self.uprompt) 50 | self.sendline("saveenv") 51 | self.expect(self.uprompt) 52 | self.sendline("print") 53 | self.expect(self.uprompt) 54 | self.sendline('run bootcmd') 55 | -------------------------------------------------------------------------------- /devices/qcom_dakota_nand.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import common 9 | import qcom_akronite_nand 10 | 11 | 12 | class QcomDakotaRouterNAND(qcom_akronite_nand.QcomAkroniteRouterNAND): 13 | ''' 14 | Board with a Dakota processor. 15 | ''' 16 | 17 | uboot_ddr_addr = "0x88000000" 18 | machid_table = {"dk03": "8010100", "dk04-nand": "8010001", "dk06-nand": "8010005", "dk07-nand": "8010006", "ea8300": "8010006"} 19 | uboot_network_delay = 5 20 | 21 | def boot_linux_ramboot(self): 22 | '''Boot Linux from initramfs''' 23 | common.print_bold("\n===== Booting linux (ramboot) for %s =====" % self.model) 24 | 25 | bootargs = 'console=ttyMSM0,115200 clk_ignore_unused norootfssplit mem=256M %s' % self.get_safe_mtdparts() 26 | if self.boot_dbg: 27 | bootargs += " dyndbg=\"module %s +p\"" % self.boot_dbg 28 | 29 | self.sendline("setenv bootargs '%s'" % bootargs) 30 | self.expect(self.uprompt) 31 | self.sendline('set fdt_high 0x85000000') 32 | self.expect(self.uprompt) 33 | self.sendline("bootm %s" % self.uboot_ddr_addr) 34 | self.expect("Loading Device Tree to") 35 | self.rambooted = True 36 | -------------------------------------------------------------------------------- /devices/qcom_dakota_nor.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import common 9 | import qcom_arm_base 10 | 11 | class QcomDakotaRouterNOR(qcom_arm_base.QcomArmBase): 12 | ''' 13 | Board with an Dakota processor. 14 | ''' 15 | 16 | uboot_ddr_addr = "0x88000000" 17 | machid_table = {"dk01-nor": "8010000", "dk04-nor": "8010001"} 18 | 19 | def __init__(self, *args, **kwargs): 20 | super(QcomDakotaRouterNOR, self).__init__(*args, **kwargs) 21 | if self.model in self.machid_table: 22 | self.machid = self.machid_table[self.model] 23 | else: 24 | raise Exception("Unknown machid for %s, please add to table" % self.model) 25 | 26 | def flash_rootfs(self, ROOTFS): 27 | common.print_bold("\n===== Flashing rootfs =====\n") 28 | filename = self.prepare_file(ROOTFS) 29 | 30 | size = self.tftp_get_file_uboot(self.uboot_ddr_addr, filename) 31 | self.spi_flash_bin(self.rootfs_addr, size, self.uboot_ddr_addr, self.rootfs_size) 32 | 33 | def flash_linux(self, KERNEL): 34 | common.print_bold("\n===== Flashing linux =====\n") 35 | filename = self.prepare_file(KERNEL) 36 | 37 | size = self.tftp_get_file_uboot(self.uboot_ddr_addr, filename) 38 | self.spi_flash_bin(self.kernel_addr, size, self.uboot_ddr_addr, self.kernel_size) 39 | 40 | def boot_linux(self, rootfs=None): 41 | common.print_bold("\n===== Booting linux for %s on %s =====" % (self.model, self.root_type)) 42 | self.reset() 43 | self.wait_for_boot() 44 | 45 | self.sendline("setenv bootcmd bootipq") 46 | self.expect(self.uprompt) 47 | self.sendline("setenv bootargs") 48 | self.expect(self.uprompt) 49 | self.sendline("saveenv") 50 | self.expect(self.uprompt) 51 | self.sendline("print") 52 | self.expect(self.uprompt) 53 | self.sendline('run bootcmd') 54 | # if run isn't support, we just reset u-boot and 55 | # let the bootcmd run that way 56 | try: 57 | self.expect('Unknown command', timeout=5) 58 | except: 59 | pass 60 | else: 61 | self.sendline('reset') 62 | -------------------------------------------------------------------------------- /devices/qcom_mips.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import common 9 | import openwrt_router 10 | 11 | 12 | class QcomMipsRouter(openwrt_router.OpenWrtRouter): 13 | ''' 14 | Board with a MIPS processor. 15 | ''' 16 | 17 | prompt = ['root\\@.*:.*#', ] 18 | uprompt = ['ath>', 'ar7240>'] 19 | 20 | def __init__(self, *args, **kwargs): 21 | super(QcomMipsRouter, self).__init__(*args, **kwargs) 22 | if self.model in ("ap152", "ap152-8M"): 23 | self.lan_gmac_iface = "eth0.1" 24 | self.wan_iface = "eth0.2" 25 | 26 | def check_memory_addresses(self): 27 | '''Before flashing an image, set memory addresses.''' 28 | if self.model in ("ap135", "ap147", "ap152", "ap151-16M"): 29 | # would be nice to dynamically detect these 30 | self.kernel_addr = "0x9fe80000" 31 | self.rootfs_addr = "0x9f050000" 32 | elif self.model in ("db120", "ap143", "ap151", "ap152-8M"): 33 | self.kernel_addr = "0x9f680000" 34 | self.rootfs_addr = "0x9f050000" 35 | elif self.model in ("tew-823dru"): 36 | self.kernel_addr = "0x9f040000" 37 | # rootfs undefined so we throw exception when trying to 38 | # write for the time being 39 | self.saveenv_safe = False 40 | 41 | def flash_rootfs(self, ROOTFS): 42 | '''Flash Root File System image''' 43 | common.print_bold("\n===== Flashing rootfs =====\n") 44 | filename = self.prepare_file(ROOTFS) 45 | if self.model == "ap135-nand": 46 | self.tftp_get_file_uboot("0x82060000", filename) 47 | self.sendline('nand erase 0x700000 0x1E00000') 48 | self.expect('OK') 49 | self.expect(self.uprompt) 50 | self.sendline('nand write.jffs2 0x82060000 0x700000 $filesize') 51 | self.expect('OK') 52 | self.expect(self.uprompt) 53 | # erase the overlay otherwise, things will be in a weird state 54 | self.sendline('nand erase 0x1F00000') 55 | self.expect('OK') 56 | self.expect(self.uprompt) 57 | return 58 | self.tftp_get_file_uboot("0x82060000", filename) 59 | self.sendline('erase %s +$filesize' % self.rootfs_addr) 60 | self.expect('Erased .* sectors', timeout=180) 61 | self.expect(self.uprompt) 62 | self.sendline('protect off all') 63 | self.expect(self.uprompt) 64 | self.sendline('cp.b $fileaddr %s $filesize' % self.rootfs_addr) 65 | self.expect('done', timeout=80) 66 | self.expect(self.uprompt) 67 | self.sendline('cmp.b $fileaddr %s $filesize' % self.rootfs_addr) 68 | self.expect('Total of .* bytes were the same') 69 | 70 | def flash_linux(self, KERNEL): 71 | common.print_bold("\n===== Flashing linux =====\n") 72 | filename = self.prepare_file(KERNEL) 73 | self.tftp_get_file_uboot("0x82060000", filename) 74 | if self.model == "ap135-nand": 75 | self.sendline('nand erase 0x100000 $filesize') 76 | self.expect('OK') 77 | self.expect(self.uprompt) 78 | self.sendline('nand write.jffs2 0x82060000 0x100000 $filesize') 79 | self.expect('OK') 80 | self.expect(self.uprompt) 81 | return 82 | self.sendline('erase %s +$filesize' % self.kernel_addr) 83 | self.expect('Erased .* sectors', timeout=120) 84 | self.expect(self.uprompt) 85 | self.sendline('protect off all') 86 | self.expect(self.uprompt) 87 | self.sendline('cp.b $fileaddr %s $filesize' % self.kernel_addr) 88 | self.expect('done', timeout=60) 89 | self.sendline('cmp.b $fileaddr %s $filesize' % self.kernel_addr) 90 | self.expect('Total of .* bytes were the same') 91 | self.expect(self.uprompt) 92 | 93 | def boot_linux(self, rootfs=None): 94 | common.print_bold("\n===== Booting linux for %s on %s =====" % (self.model, self.root_type)) 95 | if self.model == "ap135-nand": 96 | self.sendline('setenv bootcmd nboot 0x81000000 0 0x100000') 97 | self.expect(self.uprompt) 98 | else: 99 | self.sendline("setenv bootcmd 'bootm %s'" % self.kernel_addr) 100 | self.expect(self.uprompt) 101 | if self.saveenv_safe: 102 | self.sendline("saveenv") 103 | self.expect(self.uprompt) 104 | self.sendline("print") 105 | self.expect(self.uprompt) 106 | self.sendline("boot") 107 | 108 | def perf_args(self, events, kernel_user="ku"): 109 | if len(events) > 4: 110 | raise Exception("only 4 events at a time are supported") 111 | 112 | ret = [] 113 | for e in events: 114 | if e == "cycles": 115 | ret.append("cycles") 116 | elif e == "instructions": 117 | ret.append("instructions") 118 | elif e == "dcache_misses": 119 | ret.append("r98") 120 | elif e == "icache_misses": 121 | ret.append("r86") 122 | else: 123 | raise Exception("Unknown perf event %s" % e) 124 | 125 | return (':%s,' % kernel_user).join(ret) + ":%s" % kernel_user 126 | 127 | def parse_perf_board(self): 128 | events = [{'expect': 'cycles', 'name': 'cycles', 'sname': 'CPP'}, 129 | {'expect': 'instructions', 'name': 'instructions', 'sname': 'IPP'}, 130 | {'expect': 'r98:ku', 'name': 'dcache_misses', 'sname': 'DMISS'}, 131 | {'expect': 'r86:ku', 'name': 'icache_misses', 'sname': 'IMISS'}] 132 | 133 | return events 134 | -------------------------------------------------------------------------------- /devices/rpi.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import common 9 | import openwrt_router 10 | 11 | 12 | class RPI(openwrt_router.OpenWrtRouter): 13 | ''' 14 | Raspberry pi board 15 | ''' 16 | 17 | wan_iface = "erouter0" 18 | lan_iface = "brlan0" 19 | 20 | uprompt = ["U-Boot>"] 21 | uboot_eth = "sms0" 22 | uboot_ddr_addr = "0x1000000" 23 | uboot_net_delay = 0 24 | 25 | fdt = "uImage-bcm2710-rpi-3-b.dtb" 26 | fdt_overlay = "uImage-pi3-disable-bt-overlay.dtb" 27 | 28 | # can't get u-boot to work without a delay 29 | delaybetweenchar = 0.05 30 | 31 | def flash_uboot(self, uboot): 32 | '''In this case it's flashing the vfat partition of the bootload. 33 | Need to have that image u-boot and serial turned on via dtoverlay 34 | for things to work after flashing''' 35 | common.print_bold("\n===== Flashing bootloader (and u-boot) =====\n") 36 | filename = self.prepare_file(uboot) 37 | size = self.tftp_get_file_uboot(self.uboot_ddr_addr, filename) 38 | 39 | self.sendline('mmc part') 40 | # get offset of ext (83) partition after a fat (0c) partition 41 | self.expect('\r\n\s+\d+\s+(\d+)\s+(\d+).*0c( Boot)?\r\n') 42 | start = hex(int(self.match.groups()[0])) 43 | if (int(size) != int(self.match.groups()[1]) * 512): 44 | raise Exception("Partition size does not match, refusing to flash") 45 | self.expect(self.uprompt) 46 | count = hex(int(size/512)) 47 | self.sendline('mmc erase %s %s' % (start, count)) 48 | self.expect(self.uprompt) 49 | self.sendline('mmc write %s %s %s' % (self.uboot_ddr_addr, start, count)) 50 | self.expect(self.uprompt, timeout=120) 51 | 52 | self.reset() 53 | self.wait_for_boot() 54 | self.setup_uboot_network() 55 | 56 | def flash_rootfs(self, ROOTFS): 57 | common.print_bold("\n===== Flashing rootfs =====\n") 58 | filename = self.prepare_file(ROOTFS) 59 | 60 | size = self.tftp_get_file_uboot(self.uboot_ddr_addr, filename, timeout=220) 61 | self.sendline('mmc part') 62 | # get offset of ext (83) partition after a fat (0c) partition 63 | self.expect('0c( Boot)?\r\n\s+\d+\s+(\d+)\s+(\d+).*83\r\n') 64 | start = hex(int(self.match.groups()[-2])) 65 | sectors = int(self.match.groups()[-1]) 66 | self.expect(self.uprompt) 67 | 68 | # increase partition size if required 69 | if (int(size) > (sectors * 512)): 70 | self.sendline("mmc read %s 0 1" % self.uboot_ddr_addr) 71 | self.expect(self.uprompt) 72 | gp2_sz = int(self.uboot_ddr_addr, 16) + int("0x1da", 16) 73 | self.sendline("mm 0x%08x" % gp2_sz) 74 | self.expect("%08x: %08x ?" % (gp2_sz, sectors)) 75 | # pad 100M 76 | self.sendline('0x%08x' % int((int(size) + 104857600) / 512)) 77 | self.sendcontrol('c') 78 | self.sendcontrol('c') 79 | self.expect(self.uprompt) 80 | self.sendline('echo FOO') 81 | self.expect_exact('echo FOO') 82 | self.expect_exact('FOO') 83 | self.expect(self.uprompt) 84 | self.sendline("mmc write %s 0 1" % self.uboot_ddr_addr) 85 | self.expect(self.uprompt) 86 | self.sendline('mmc rescan') 87 | self.expect(self.uprompt) 88 | self.sendline('mmc part') 89 | self.expect(self.uprompt) 90 | 91 | count = hex(int(size/512)) 92 | self.sendline('mmc erase %s %s' % (start, count)) 93 | self.expect(self.uprompt) 94 | self.sendline('mmc write %s %s %s' % (self.uboot_ddr_addr, start, count)) 95 | self.expect_exact('mmc write %s %s %s' % (self.uboot_ddr_addr, start, count)) 96 | self.expect(self.uprompt, timeout=120) 97 | 98 | def flash_linux(self, KERNEL): 99 | common.print_bold("\n===== Flashing linux =====\n") 100 | 101 | filename = self.prepare_file(KERNEL) 102 | size = self.tftp_get_file_uboot(self.uboot_ddr_addr, filename) 103 | 104 | self.sendline('fatwrite mmc 0 %s uImage $filesize' % self.uboot_ddr_addr) 105 | self.expect(self.uprompt) 106 | 107 | def boot_linux(self, rootfs=None): 108 | common.print_bold("\n===== Booting linux for %s on %s =====" % (self.model, self.root_type)) 109 | 110 | #self.sendline('setenv bootargs "8250.nr_uarts=1 bcm2708_fb.fbwidth=1824 bcm2708_fb.fbheight=984 bcm2708_fb.fbswap=1 dma.dmachans=0x7f35 bcm2709.boardrev=0xa02082 bcm2709.serial=0xc07187c2 bcm2709.uart_clock=48000000 smsc95xx.macaddr=B8:27:EB:71:87:C2 vc_mem.mem_base=0x3dc00000 vc_mem.mem_size=0x3f000000 dwc_otg.lpm_enable=0 console=ttyAMA0,115200 root=mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait"') 111 | #self.expect(self.uprompt) 112 | 113 | self.sendline("setenv bootcmd 'fatload mmc 0 ${kernel_addr_r} uImage; bootm ${kernel_addr_r} - ${fdt_addr}'") 114 | self.expect(self.uprompt) 115 | self.sendline('saveenv') 116 | self.expect(self.uprompt) 117 | self.sendline('boot') 118 | 119 | # Linux handles serial better ? 120 | self.delaybetweenchar = None 121 | -------------------------------------------------------------------------------- /devices/ser2net_connection.py: -------------------------------------------------------------------------------- 1 | import pexpect 2 | 3 | class Ser2NetConnection(): 4 | def __init__(self, device=None, conn_cmd=None, **kwargs): 5 | self.device = device 6 | self.conn_cmd = conn_cmd 7 | 8 | def connect(self): 9 | pexpect.spawn.__init__(self.device, 10 | command='/bin/bash', 11 | args=['-c', self.conn_cmd]) 12 | 13 | try: 14 | result = self.device.expect(["assword:", "ser2net.*\r\n", "OpenGear Serial Server"]) 15 | except pexpect.EOF as e: 16 | raise Exception("Board is in use (connection refused).") 17 | if result == 0: 18 | raise Exception("Password required and not supported") 19 | 20 | def close(): 21 | self.device.sendline("~.") 22 | -------------------------------------------------------------------------------- /devices/ssh_connection.py: -------------------------------------------------------------------------------- 1 | import pexpect 2 | 3 | 4 | class SshConnection: 5 | ''' 6 | To use, set conn_cmd in your json to "ssh root@192.168.1.1 -i ~/.ssh/id_rsa"" 7 | and set connection_type to "ssh" 8 | 9 | ''' 10 | def __init__(self, device=None, conn_cmd=None, ssh_password='None', **kwargs): 11 | self.device = device 12 | self.conn_cmd = conn_cmd 13 | self.ssh_password = ssh_password 14 | 15 | def connect(self): 16 | pexpect.spawn.__init__(self.device, 17 | command='/bin/bash', 18 | args=['-c', self.conn_cmd]) 19 | 20 | try: 21 | result = self.device.expect(["assword:", "passphrase"] + self.device.prompt) 22 | except pexpect.EOF as e: 23 | raise Exception("Board is in use (connection refused).") 24 | if result == 0 or result == 1: 25 | assert self.ssh_password is not None, "Please add ssh_password in your json configuration file." 26 | self.device.sendline(self.ssh_password) 27 | self.device.expect(self.device.prompt) 28 | 29 | def close(self): 30 | self.device.sendline('exit') 31 | -------------------------------------------------------------------------------- /docs/BoardFarm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qca/boardfarm/1b44ba8924be7447412e1f053e3aeadb4bc84164/docs/BoardFarm.png -------------------------------------------------------------------------------- /docs/ELK_SETUP.md: -------------------------------------------------------------------------------- 1 | ELK Setup 2 | ========= 3 | 4 | ELK is the short term for a software compilation used by BoardFarm to store, process and visualize test results. 5 | ELK consists of three tools: 6 | 7 | * Elasticsearch 8 | * Logstash 9 | * Kibana 10 | 11 | For this basic setup guide, we won't even need Logstash as the results are already stored in the correct format in the Elasticsearch database. 12 | 13 | Installation 14 | ------------ 15 | 16 | This guide was written for Ubuntu 16.04 LTS and the following versions of Elasticsearch and Kibana: 17 | 18 | * Elasticsearch 2.3.3 19 | * Kibana 4.5.1 20 | 21 | If you follow this guide and you can't get it working with newer versions, you should try again with these versions. 22 | 23 | ### Prerequisites 24 | 25 | Elasticsearch and Logstash require Java. Elasticsearch recommends Oracle Java 8 but I had no issues using OpenJDK 8. Use whatever you prefer, I'll use OpenJDK in this guide. 26 | 27 | apt-get install -y openjdk-8-jre openjdk-8-jdk 28 | 29 | Now that we've installed Java, we can go on with Elasticsearch. 30 | 31 | ### Installing Elasticsearch 32 | 33 | We'll just download the Elasticsearch Debian package and install it: 34 | 35 | wget https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/deb/elasticsearch/2.3.3/elasticsearch-2.3.3.deb 36 | dpkg -i ./elasticsearch-2.3.3.deb 37 | 38 | You probably want Elasticsearch to automatically start every time you reboot your machine: 39 | 40 | systemctl enable elasticsearch 41 | 42 | Now let's edit the Elasticsearch configuration to restrict access from anywhere except the host itself: 43 | 44 | nano /etc/elasticsearch/elasticsearch.yml 45 | 46 | Look for a line containing 'network.host' and uncomment it. Set the value to 'localhost': 47 | 48 | network.host: localhost 49 | 50 | This will only allow the machine itself to access Elasticsearch on port 9200. Otherwise, anyone with network access to the machine could access your stored data or shut down the Elasticsearch server. 51 | 52 | Finally, restart Elasticsearch to apply the changed settings: 53 | 54 | systemctl restart elasticsearch 55 | 56 | ### Installing Kibana 57 | 58 | Kibana is available as a 32-bit package and a 64-bit package, I'm using the 64-bit package in this guide: 59 | 60 | wget https://download.elastic.co/kibana/kibana/kibana_4.5.1_amd64.deb 61 | dpkg -i ./kibana_4.5.1_amd64.deb 62 | 63 | As we did with Elasticsearch, we'll make Kibana automatically start on bootup: 64 | 65 | systemctl enable kibana 66 | 67 | You should now be able to access the Kibana webinterface on port 5601. Before visiting the webinterface for the first time, we want to store some data in the Elasticsearch database so Kibana has something to work with when we start it for the first time. 68 | 69 | ### Configuring BoardFarm 70 | 71 | We need to tell BoardFarm to store the data in the Elasticsearch database by specifying the server address in BoardFarm's `config.py`. In this guide, I'm running BoardFarm, Elasticsearch and Kibana on the same host, so I'll use 'localhost' as the server address: 72 | 73 | elasticsearch_server = 'localhost' 74 | 75 | You should then run a few BoardFarm tests to store some data in the Elasticsearch database. After running the tests, you should see something like this at the end of the output: 76 | 77 | Elasticsearch: Data stored at localhost boardfarm-2016.07.05/bft_run/AVW6ozxZoiJNDeD5bFyj 78 | 79 | Now, we have data to work with. 80 | 81 | ### Specifying an index pattern in Kibana 82 | 83 | When visiting the Kibana webinterface for the first time, you'll have to specify an index pattern so Kibana knows which logfiles to analyze. BoardFarm stores its data in the format boardfarm-\, so we'll tell Kibana to use 'boardfarm-\*' as the index pattern. 84 | Kibana should then automatically recognize your existing database entries and suggest '@timestamp' as the time-field name. 85 | Click 'create' and you're good to go. You should now see a table with a number of fields, e.g. 'board_type' or 'tests_total'. 86 | 87 | *Note: If you now run other testcases with new field names, you'll have to click the 'refresh field list' button at the top of the page, otherwise the new fields won't be displayed.* 88 | 89 | You can now go to the 'Discover' page to see your log entries or to the 'Visualize' page to create new visualizations. 90 | -------------------------------------------------------------------------------- /docs/Simple_Board_Farm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qca/boardfarm/1b44ba8924be7447412e1f053e3aeadb4bc84164/docs/Simple_Board_Farm.jpg -------------------------------------------------------------------------------- /html/template_results.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | Results 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 33 | 34 |
26 |

${summary_title}

27 | Board: ${board_type} 28 |
31 | 32 |
35 | 36 |
37 | 38 | 39 | 40 | ${table_summary_results} 41 |
Summary
42 | 43 |
44 | 45 | 46 | 47 | ${table_fail_results} 48 |
Failures
49 | 50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ${table_results} 61 |
Full Results
#ResultNameDescription
62 | 63 | Total test time: ${total_test_time}.
64 |
65 |
66 | See full test output or see parameters and retest.
67 |
68 | Board access:
69 |
    70 |
  • Name: ${station}
  • 71 |
  • Router console: ${conn_cmd}
  • 72 |
  • Lan Device: ssh root@${lan_device}
  • 73 |
  • Wan Device: ssh root@${wan_device}
  • 74 |
  • WebGUI: use HTTP Proxy "${lan_device}", Port "8080"
  • 75 |
76 | Changes: ${changes} 77 |
78 | Questions? Email boardfarm-admins. 79 |
80 | 81 | 82 | -------------------------------------------------------------------------------- /html/template_results_basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | Results 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 33 | 34 |
26 |

${summary_title}

27 | Board: ${board_type} 28 |
31 | 32 |
35 | 36 |
37 | 38 | 39 | 40 | ${table_summary_results} 41 |
Summary
42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | ${table_results} 54 |
Full Results
#ResultNameDescription
55 | 56 | Total test time: ${total_test_time}.
57 |
58 |
59 | See full test output or see parameters and retest.
60 | Station: ${location} ${station}. 61 |

62 |
63 | Questions? Email boardfarm-admins. 64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /library.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import os 9 | 10 | from termcolor import cprint 11 | 12 | def print_bold(msg): 13 | cprint(msg, None, attrs=['bold']) 14 | 15 | def print_board_info(x): 16 | for key in sorted(x): 17 | print_bold(" %s: %s" % (key, x[key])) 18 | 19 | def process_test_results(raw_test_results): 20 | full_results = {'test_results': [], 21 | 'tests_pass': 0, 22 | 'tests_fail': 0, 23 | 'tests_skip': 0, 24 | 'tests_total': 0, 25 | } 26 | for i, x in enumerate(raw_test_results): 27 | message = None 28 | name = x.__class__.__name__ 29 | grade = None 30 | try: 31 | grade = x.result_grade 32 | except: 33 | pass 34 | if grade == "OK" or grade == "Unexp OK": 35 | full_results['tests_pass'] += 1 36 | elif grade == "FAIL" or grade == "Exp FAIL": 37 | full_results['tests_fail'] += 1 38 | elif grade == "SKIP" or grade is None: 39 | full_results['tests_skip'] += 1 40 | try: 41 | # Use only first line of docstring result message 42 | message = x.__doc__.split('\n')[0] 43 | except: 44 | print_bold("WARN: Please add docstring to %s." % x) 45 | try: 46 | message = x.result_message 47 | except: 48 | pass 49 | if hasattr(x, 'long_result_message'): 50 | long_message = x.long_result_message 51 | else: 52 | long_message = "" 53 | full_results['test_results'].append({"name": name, "message": message, "long_message": long_message, "grade": grade}) 54 | full_results['tests_total'] = len(raw_test_results) 55 | return full_results 56 | 57 | def send_results_to_myqsl(testsuite, output_dir): 58 | ''' 59 | Send url of results to a MySQL database. Only do this if we are on 60 | a build server (use the build environment variables). 61 | ''' 62 | dir = output_dir.replace(os.getcwd(), '').strip(os.sep) 63 | build_id = os.environ.get('image_build_id', '') 64 | build_url = os.environ.get('BUILD_URL', '') 65 | if '' not in (build_id, testsuite, build_url): 66 | from devices import mysql 67 | build_url = build_url.replace("https://", "") + "artifact/openwrt/%s/results.html" % dir 68 | title = 'Board Farm Results (suite: %s)' % testsuite 69 | reporter = mysql.MySqlReporter() 70 | reporter.insert_data(build_id, build_url, title) 71 | -------------------------------------------------------------------------------- /make_human_readable.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2015 4 | # 5 | # All rights reserved. 6 | # 7 | # This file is distributed under the Clear BSD license. 8 | # The full text can be found in LICENSE in the root directory. 9 | 10 | import json 11 | import os 12 | import re 13 | import sys 14 | 15 | from string import Template 16 | try: 17 | from collections import Counter 18 | except: 19 | from future.moves.collections import Counter 20 | 21 | import config 22 | 23 | owrt_tests_dir = os.path.dirname(os.path.realpath(__file__)) 24 | 25 | def pick_template_filename(): 26 | ''' 27 | Decide which HTML file to use as template for results. 28 | This allows for different format for different audiences. 29 | ''' 30 | templates = {'basic': owrt_tests_dir+"/html/template_results_basic.html", 31 | 'full': owrt_tests_dir+"/html/template_results.html"} 32 | if os.environ.get('test_suite') == 'daily_au': 33 | return templates['basic'] 34 | else: 35 | return templates['full'] 36 | 37 | def changes_to_html(changes): 38 | ''' 39 | Input: "15408,8 17196,2 17204,1" 40 | Output: String of html links, e.g. 41 | 15408,8, 42 | %(num)s%(grade)s%(name)s' % t) 100 | results_table_lines.append('%(num)s%(grade)s%(name)s%(message)s' % t) 101 | if t['long_message'] != "": 102 | results_table_lines.append('
' % t)
103 |             results_table_lines.append("%(long_message)s" % t)
104 |             results_table_lines.append('
') 105 | 106 | # process the summary counter 107 | results_summary_table_lines = [] 108 | for e, v in grade_counter.items(): 109 | t['style'] = styles[t['grade']] 110 | results_summary_table_lines.append('%s: %d' % (styles[e], e, v)) 111 | 112 | # Create the results tables 113 | parameters['table_results'] = "\n".join(results_table_lines) 114 | if len(results_fail_table_lines) == 0: 115 | parameters['table_fail_results'] = "None" 116 | else: 117 | parameters['table_fail_results'] = "\n".join(results_fail_table_lines) 118 | parameters['table_summary_results'] = "\n".join(results_summary_table_lines) 119 | 120 | # Other parameters 121 | try: 122 | test_seconds = int(os.environ.get('TEST_END_TIME'))-int(os.environ.get('TEST_START_TIME')) 123 | parameters['total_test_time'] = "%s minutes" % (test_seconds/60) 124 | except: 125 | pass 126 | # Substitute parameters into template html to create new html file 127 | template_filename = pick_template_filename() 128 | f = open(template_filename, "r").read() 129 | s = Template(f) 130 | f = open(output_name, "w") 131 | f.write(s.substitute(parameters)) 132 | f.close() 133 | print("Created %s" % output_name) 134 | 135 | def get_title(): 136 | try: 137 | title = os.environ.get('summary_title') 138 | if title: 139 | return title 140 | except: 141 | pass 142 | try: 143 | return os.environ.get('JOB_NAME') 144 | except: 145 | return None 146 | 147 | if __name__ == '__main__': 148 | try: 149 | list_results = json.load(open(sys.argv[1], 'r'))['test_results'] 150 | xmlresults_to_html(list_results, title="Test Results") 151 | except Exception as e: 152 | print(e) 153 | print("To use make_human_readable.py:") 154 | print("./make_human_readable.py results/test_results.json") 155 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | argparse 2 | elasticsearch>=1.0.0 3 | unittest2 4 | pexpect==3.1 5 | junitxml 6 | beautifulsoup4 7 | termcolor 8 | selenium 9 | future 10 | dlipower 11 | -------------------------------------------------------------------------------- /results/README: -------------------------------------------------------------------------------- 1 | This is a placeholder file so this directory exists, all results 2 | will be stored here 3 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | import lib 8 | 9 | # Import from every file 10 | import os 11 | import glob 12 | test_files = glob.glob(os.path.dirname(__file__)+"/*.py") 13 | for x in sorted([os.path.basename(f)[:-3] for f in test_files if not "__" in f]): 14 | try: 15 | exec("from %s import *" % x) 16 | except Exception as e: 17 | print(e) 18 | print("Warning: could not import from file %s." % x) 19 | -------------------------------------------------------------------------------- /tests/bridge_mode.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | import lib 10 | from devices import board, wan, lan, wlan, prompt 11 | 12 | class BridgedMode(rootfs_boot.RootFSBootTest): 13 | '''Puts router in bridged mode (other tests may not work after running this)''' 14 | def runTest(self): 15 | board.sendline('uci set network.lan.ifname="%s %s"' % (board.wan_iface, board.lan_gmac_iface)) 16 | board.expect(prompt) 17 | board.sendline('uci set firewall.@defaults[0]=defaults') 18 | board.expect(prompt) 19 | board.sendline('uci set firewall.@defaults[0].input=ACCEPT') 20 | board.expect(prompt) 21 | board.sendline('uci set firewall.@defaults[0].output=ACCEPT') 22 | board.expect(prompt) 23 | board.sendline('uci set firewall.@defaults[0].syn_flood=1') 24 | board.expect(prompt) 25 | board.sendline('uci set firewall.@defaults[0].forward=ACCEPT') 26 | board.expect(prompt) 27 | board.sendline('uci set firewall.@zone[0]=zone') 28 | board.expect(prompt) 29 | board.sendline('uci set firewall.@zone[0].name=lan') 30 | board.expect(prompt) 31 | board.sendline('uci set firewall.@zone[0].network=lan') 32 | board.expect(prompt) 33 | board.sendline('uci set firewall.@zone[0].input=ACCEPT') 34 | board.expect(prompt) 35 | board.sendline('uci set firewall.@zone[0].output=ACCEPT') 36 | board.expect(prompt) 37 | board.sendline('uci set firewall.@zone[0].forward=ACCEPT') 38 | board.expect(prompt) 39 | board.sendline('uci set firewall.@zone[1]=zone') 40 | board.expect(prompt) 41 | board.sendline('uci set firewall.@zone[1].name=wan') 42 | board.expect(prompt) 43 | board.sendline('uci set firewall.@zone[1].network=wan') 44 | board.expect(prompt) 45 | board.sendline('uci set firewall.@zone[1].output=ACCEPT') 46 | board.expect(prompt) 47 | board.sendline('uci set firewall.@zone[1].mtu_fix=1') 48 | board.expect(prompt) 49 | board.sendline('uci set firewall.@zone[1].input=ACCEPT') 50 | board.expect(prompt) 51 | board.sendline('uci set firewall.@zone[1].forward=ACCEPT') 52 | board.expect(prompt) 53 | board.sendline('uci commit') 54 | board.expect(prompt) 55 | board.network_restart() 56 | board.firewall_restart() 57 | 58 | lan.sendline('ifconfig eth1 192.168.0.2') 59 | lan.expect(prompt) 60 | -------------------------------------------------------------------------------- /tests/connection_stress.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | import time 10 | from devices import board, wan, lan, wlan, prompt 11 | 12 | class Connection_Stress(rootfs_boot.RootFSBootTest): 13 | '''Measured CPU use while creating thousands of connections.''' 14 | def runTest(self): 15 | num_conn = 5000 16 | # Wan device: Create small file in web dir 17 | fname = 'small.txt' 18 | cmd = '\nhead -c 10000 /dev/urandom > /var/www/%s' % fname 19 | wan.sendline(cmd) 20 | wan.expect(prompt) 21 | # Lan Device: download small file a lot 22 | concurrency = 25 23 | url = 'http://192.168.0.1/%s' % fname 24 | # Start CPU monitor 25 | board.sendline('\nmpstat -P ALL 10000 1') 26 | # Lan Device: download small file a lot 27 | lan.sendline('\nab -dn %s -c %s %s' % (num_conn, concurrency, url)) 28 | lan.expect('Benchmarking', timeout=5) 29 | lan.expect('Requests per second:\s+(\d+)') 30 | reqs_per_sec = int(lan.match.group(1)) 31 | lan.expect(prompt) 32 | # Stop CPU monitor 33 | board.sendcontrol('c') 34 | board.expect('Average:\s+all(\s+[0-9]+.[0-9]+){10}\r\n') 35 | idle_cpu = float(board.match.group(1)) 36 | avg_cpu = 100 - float(idle_cpu) 37 | board.expect(prompt) 38 | msg = "ApacheBench measured %s connections/second, CPU use = %s%%." % (reqs_per_sec, avg_cpu) 39 | self.result_message = msg 40 | time.sleep(5) # Give router a few seconds to recover 41 | def recover(self): 42 | board.sendcontrol('c') 43 | board.expect(prompt) 44 | lan.sendcontrol('c') 45 | time.sleep(2) # Give router a few seconds to recover 46 | -------------------------------------------------------------------------------- /tests/curl_https.py: -------------------------------------------------------------------------------- 1 | import rootfs_boot 2 | from devices import board, wan, lan, wlan, prompt 3 | 4 | class CurlSSLGood(rootfs_boot.RootFSBootTest): 5 | '''Curl can access https and verify signature.''' 6 | def runTest(self): 7 | board.sendline('\n') 8 | board.expect(prompt) 9 | board.sendline('opkg install ca-certificates') 10 | board.expect(prompt) 11 | checks = [ 12 | 'https://sha256.badssl.com/', 13 | 'https://1000-sans.badssl.com/', 14 | 'https://mozilla-modern.badssl.com/', 15 | 'https://dh2048.badssl.com/', 16 | 'https://hsts.badssl.com/', 17 | 'https://upgrade.badssl.com/', 18 | 'https://preloaded-hsts.badssl.com/', 19 | ] 20 | for check in checks: 21 | board.sendline('curl ' + check) 22 | board.expect('') 23 | board.expect(prompt) 24 | print '\n\nCurl downloaded ' + check + ' as expected\n' 25 | 26 | class CurlSSLBad(rootfs_boot.RootFSBootTest): 27 | '''Curl can't access https with bad signature.''' 28 | def runTest(self): 29 | board.sendline('\n') 30 | board.expect(prompt) 31 | board.sendline('opkg install ca-certificates') 32 | board.expect(prompt) 33 | checks = [ 34 | ('https://expired.badssl.com/', 'certificate has expired'), 35 | ('https://wrong.host.badssl.com/', 'no alternative certificate subject name matches target host name'), 36 | ('https://subdomain.preloaded-hsts.badssl.com/', 'no alternative certificate subject name matches target host name'), 37 | ('https://self-signed.badssl.com/', 'unable to get local issuer certificate'), 38 | ('https://superfish.badssl.com/', 'unable to get local issuer certificate'), 39 | ('https://edellroot.badssl.com/', 'unable to get local issuer certificate'), 40 | ('https://dsdtestprovider.badssl.com/', 'unable to get local issuer certificate'), 41 | ('https://incomplete-chain.badssl.com/', 'unable to get local issuer certificate'), 42 | ] 43 | for check in checks: 44 | board.sendline('curl ' + check[0]) 45 | board.expect(check[1]) 46 | board.expect(prompt) 47 | print '\n\nCurl refused to download ' + check[0] + ' as expected\n' 48 | 49 | -------------------------------------------------------------------------------- /tests/firewall_on_off.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | from devices import board, wan, lan, wlan, prompt 10 | 11 | class FirewallOFF(rootfs_boot.RootFSBootTest): 12 | '''Turned router firewall off.''' 13 | def runTest(self): 14 | board.sendline('\nuci set firewall.@defaults[0].forward=ACCEPT') 15 | board.expect('uci set firewall') 16 | board.expect(prompt) 17 | board.sendline('uci set firewall.@zone[0].forward=ACCEPT') 18 | board.expect(prompt) 19 | board.sendline('uci set firewall.@zone[1].input=ACCEPT') 20 | board.expect(prompt) 21 | board.sendline('uci set firewall.@zone[1].forward=ACCEPT') 22 | board.expect(prompt) 23 | board.sendline('uci commit firewall') 24 | board.expect(prompt) 25 | board.firewall_restart() 26 | 27 | class FirewallON(rootfs_boot.RootFSBootTest): 28 | '''Turned router firewall on.''' 29 | def runTest(self): 30 | board.sendline('\nuci set firewall.@defaults[0].forward=REJECT') 31 | board.expect('uci set firewall') 32 | board.expect(prompt) 33 | board.sendline('uci set firewall.@zone[0].forward=REJECT') 34 | board.expect(prompt) 35 | board.sendline('uci set firewall.@zone[1].input=REJECT') 36 | board.expect(prompt) 37 | board.sendline('uci set firewall.@zone[1].forward=REJECT') 38 | board.expect(prompt) 39 | board.sendline('uci commit firewall') 40 | board.expect(prompt) 41 | board.firewall_restart() 42 | -------------------------------------------------------------------------------- /tests/igmpv3_basic.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | from devices import board, wan, lan, wlan, prompt 10 | 11 | class IGMPv3_Running(rootfs_boot.RootFSBootTest): 12 | '''IGMP Proxy daemon mcproxy is up and running.''' 13 | def runTest(self): 14 | board.sendline('\nps | grep mcproxy') 15 | board.expect('/usr/sbin/mcproxy -f /etc/mcproxy.conf', timeout=5) 16 | board.expect(prompt) 17 | 18 | class IGMPv3_Config(rootfs_boot.RootFSBootTest): 19 | '''IGMP Proxy daemon mcproxy config is set correctly.''' 20 | def runTest(self): 21 | board.sendline('\nuci show mcproxy') 22 | board.expect_exact('mcproxy.config=mcproxy', timeout=5) 23 | board.expect_exact('mcproxy.config.protocol=IGMPv3', timeout=5) 24 | board.expect_exact('mcproxy.@pinstance[0]=pinstance', timeout=5) 25 | board.expect_exact('mcproxy.@pinstance[0].name=mcproxy1', timeout=5) 26 | board.expect(prompt) 27 | board.sendline('cat /etc/mcproxy.conf') 28 | board.expect('protocol IGMPv3;', timeout=5) 29 | board.expect('pinstance mcproxy1: "eth0" ==> "br-lan";') 30 | board.expect(prompt) 31 | 32 | class IGMPv3_StopStart(rootfs_boot.RootFSBootTest): 33 | '''IGMP Proxy daemon mcproxy can be stopped and started without rebooting.''' 34 | def runTest(self): 35 | board.sendline('\n/etc/init.d/mcproxy stop') 36 | board.expect(prompt) 37 | board.sendline('ps | grep mcproxy') 38 | board.expect(prompt) 39 | assert '/usr/sbin/mcproxy' not in board.before 40 | board.sendline('/etc/init.d/mcproxy start') 41 | board.sendline('ps | grep mcproxy') 42 | board.expect('/usr/sbin/mcproxy -f /etc/mcproxy.conf', timeout=5) 43 | board.expect(prompt) 44 | -------------------------------------------------------------------------------- /tests/interact.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | import lib 10 | import os 11 | import sys 12 | import glob 13 | from devices import board, wan, lan, wlan, prompt 14 | 15 | def print_subclasses(cls): 16 | for x in cls.__subclasses__(): 17 | print(x.__name__) 18 | print_subclasses(x) 19 | 20 | 21 | class Interact(rootfs_boot.RootFSBootTest): 22 | '''Interact with console, wan, lan, wlan connections and re-run tests''' 23 | 24 | def print_legacy_devices(self): 25 | print(" LAN device: ssh %s@%s" % (self.config.board.get('lan_username', "root"), self.config.board.get('lan_device'))) 26 | print(" WAN device: ssh %s@%s" % (self.config.board.get('wan_username', "root") ,self.config.board.get('wan_device'))) 27 | 28 | def print_dynamic_devices(self): 29 | for device in self.config.devices: 30 | d = getattr(self.config, device) 31 | print(" %s device: ssh %s@%s" % (device, d.username, d.name)) 32 | 33 | def runTest(self): 34 | legacy = hasattr(self.config, "wan_device") 35 | lib.common.test_msg("Press Ctrl-] to stop interaction and return to menu") 36 | board.sendline() 37 | try: 38 | board.interact() 39 | except: 40 | return 41 | 42 | while True: 43 | print("\n\nCurrent station") 44 | print(" Board console: %s" % self.config.board.get('conn_cmd')) 45 | if legacy: 46 | self.print_legacy_devices() 47 | self.print_dynamic_devices() 48 | print('Pro-tip: Increase kernel message verbosity with\n' 49 | ' echo "7 7 7 7" > /proc/sys/kernel/printk') 50 | print("Menu") 51 | print(" 1: Enter console") 52 | i = 2 53 | if legacy: 54 | print(" 2: Enter wan console") 55 | print(" 3: Enter lan console") 56 | print(" 4: Enter wlan console") 57 | i = 5 58 | 59 | print(" %s: List all tests" % i) 60 | i += 1 61 | print(" %s: Run test" % i) 62 | i += 1 63 | print(" %s: Reset board" % i) 64 | i += 1 65 | print(" %s: Enter interactive python shell" % i) 66 | i += 1 67 | if len(self.config.devices) > 0: 68 | print(" Type a device name to connect: %s" % self.config.devices) 69 | print(" x: Exit") 70 | key = raw_input("Please select: ") 71 | 72 | if key in self.config.devices: 73 | d = getattr(self.config, key) 74 | d.interact() 75 | 76 | if key == "1": 77 | board.interact() 78 | elif legacy and key == "2": 79 | wan.interact() 80 | elif legacy and key == "3": 81 | lan.interact() 82 | elif legacy and key == "4": 83 | wlan.interact() 84 | elif (legacy and key == "5") or key == "2": 85 | try: 86 | # re import the tests 87 | test_files = glob.glob(os.path.dirname(__file__)+"/*.py") 88 | for x in sorted([os.path.basename(f)[:-3] for f in test_files if not "__" in f]): 89 | exec("from %s import *" % x) 90 | except: 91 | print("Unable to re-import tests!") 92 | else: 93 | # list what we can re-run 94 | rfs_boot = rootfs_boot.RootFSBootTest 95 | print("Available tests:") 96 | print_subclasses(rfs_boot) 97 | elif (legacy and key == "6") or key == "3": 98 | try: 99 | # re import the tests 100 | test_files = glob.glob(os.path.dirname(__file__)+"/*.py") 101 | for x in sorted([os.path.basename(f)[:-3] for f in test_files if not "__" in f]): 102 | exec("from %s import *" % x) 103 | except: 104 | print("Unable to re-import tests!") 105 | else: 106 | # TODO: use an index instead of test name 107 | print("Type test to run: ") 108 | test = sys.stdin.readline() 109 | 110 | try: 111 | board.sendline() 112 | board.sendline('echo \"1 1 1 7\" > /proc/sys/kernel/printk') 113 | board.expect(prompt) 114 | t = eval(test) 115 | cls = t(self.config) 116 | lib.common.test_msg("\n==================== Begin %s ====================" % cls.__class__.__name__) 117 | cls.testWrapper() 118 | lib.common.test_msg("\n==================== End %s ======================" % cls.__class__.__name__) 119 | board.sendline() 120 | except: 121 | print("Unable to (re-)run specified test") 122 | 123 | elif (legacy and key == "7") or key == "4": 124 | board.reset() 125 | print("Press Ctrl-] to stop interaction and return to menu") 126 | board.interact() 127 | elif (legacy and key == "8") or key == "5": 128 | print "Enter python shell, press Ctrl-D to exit" 129 | try: 130 | from IPython import embed 131 | embed() 132 | except: 133 | try: 134 | import readline # optional, will allow Up/Down/History in the console 135 | import code 136 | vars = globals().copy() 137 | vars.update(locals()) 138 | shell = code.InteractiveConsole(vars) 139 | shell.interact() 140 | except: 141 | print "Unable to spawn interactive shell!" 142 | elif key == "x": 143 | break 144 | -------------------------------------------------------------------------------- /tests/ip_link.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import re 9 | import rootfs_boot 10 | from devices import board, wan, lan, wlan, prompt 11 | 12 | class InterfacesShow(rootfs_boot.RootFSBootTest): 13 | '''Used "ip" or "ifconfig" to list interfaces.''' 14 | def runTest(self): 15 | board.sendline('\nip link show') 16 | board.expect('ip link show') 17 | board.expect(prompt) 18 | if "ip: not found" not in board.before: 19 | up_interfaces = re.findall('\d: ([A-Za-z0-9-\.]+)[:@].*state UP ', board.before) 20 | else: 21 | board.sendline('ifconfig') 22 | board.expect(prompt) 23 | up_interfaces = re.findall('([A-Za-z0-9-\.]+)\s+Link', board.before) 24 | num_up = len(up_interfaces) 25 | if num_up >= 1: 26 | msg = "%s interfaces are UP: %s." % (num_up, ", ".join(sorted(up_interfaces))) 27 | else: 28 | msg = "0 interfaces are UP." 29 | self.result_message = msg 30 | assert num_up >= 2 31 | -------------------------------------------------------------------------------- /tests/iperf3_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import re 9 | import rootfs_boot 10 | from lib import installers 11 | from devices import board, wan, lan, wlan, prompt 12 | 13 | class iPerf3Test(rootfs_boot.RootFSBootTest): 14 | '''iPerf3 generic performance tests''' 15 | def runTest(self): 16 | installers.install_iperf3(wan) 17 | installers.install_iperf3(lan) 18 | 19 | wan.sendline('iperf3 -s') 20 | wan.expect('-----------------------------------------------------------') 21 | wan.expect('-----------------------------------------------------------') 22 | 23 | time = 60 24 | 25 | lan.sendline('iperf3 -c 192.168.0.1 -P5 -t %s -i 0' % time) 26 | lan.expect(prompt, timeout=time+5) 27 | 28 | sender = re.findall('SUM.*Bytes\s*(.*/sec).*sender', lan.before)[-1] 29 | if 'Mbits' in sender: 30 | s_rate = float(sender.split()[0]) 31 | elif 'Kbits' in sender: 32 | s_rate = float(sender.split()[0]/1024) 33 | else: 34 | raise Exception("Unknown rate in sender results") 35 | 36 | recv = re.findall('SUM.*Bytes\s*(.*/sec).*receiver', lan.before)[-1] 37 | if 'Mbits' in recv: 38 | r_rate = float(recv.split()[0]) 39 | elif 'Kbits' in recv: 40 | r_rate = float(recv.split()[0]/1024) 41 | else: 42 | raise Exception("Unknown rate in recv results") 43 | 44 | self.result_message = "Sender rate = %si MBits/sec, Receiver rate = %s Mbits/sec\n", (s_rate, r_rate) 45 | self.logged['s_rate'] = s_rate 46 | self.logged['r_rate'] = r_rate 47 | 48 | self.recovery() 49 | 50 | def recovery(self): 51 | for d in [wan, lan]: 52 | d.sendcontrol('c') 53 | d.sendcontrol('c') 54 | d.expect(prompt) 55 | -------------------------------------------------------------------------------- /tests/ipv6_curl.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | from devices import board, wan, lan, wlan, prompt 10 | 11 | class IPv6_File_Download(rootfs_boot.RootFSBootTest): 12 | '''Downloaded file through router using IPv6.''' 13 | def runTest(self): 14 | # WAN Device: create large file in web directory 15 | fname = "/var/www/20mb.txt" 16 | wan.sendline('\n[ -e "%s" ] || head -c 20971520 /dev/urandom > %s' % (fname, fname)) 17 | wan.expect('/var') 18 | wan.expect(prompt) 19 | # LAN Device: download the file 20 | lan.sendline('\ncurl -m 57 -g -6 http://[5aaa::6]/20mb.txt > /dev/null') 21 | lan.expect('Total', timeout=5) 22 | i = lan.expect(["couldn't connect", '20.0M 100 20.0M'], timeout=60) 23 | if i == 0: 24 | assert False 25 | lan.expect(prompt) 26 | -------------------------------------------------------------------------------- /tests/ipv6_setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import time 9 | 10 | import rootfs_boot 11 | 12 | from devices import board, wan, lan, wlan, prompt 13 | from lib.common import run_once 14 | 15 | class Set_IPv6_Addresses(rootfs_boot.RootFSBootTest): 16 | '''Set IPv6 addresses and default routes for router and devices.''' 17 | @run_once 18 | def runTest(self): 19 | # Router 20 | board.sendline('uci set network.lan6=interface') 21 | board.expect(prompt) 22 | board.sendline('uci set network.lan6.proto=static') 23 | board.expect(prompt) 24 | board.sendline('uci set network.lan6.ip6addr=4aaa::1/64') 25 | board.expect(prompt) 26 | board.sendline('uci set network.lan6.ifname=@lan') 27 | board.expect(prompt) 28 | board.sendline('uci set network.wan6=interface') 29 | board.expect(prompt) 30 | board.sendline('uci set network.wan6.proto=static') 31 | board.expect(prompt) 32 | board.sendline('uci set network.wan6.ip6addr=5aaa::1/64') 33 | board.expect(prompt) 34 | board.sendline('uci set network.wan6.ifname=@wan') 35 | board.expect(prompt) 36 | board.sendline('uci commit network') 37 | board.expect(prompt) 38 | board.network_restart() 39 | # Lan-side Device 40 | lan.sendline('\nip -6 addr add 4aaa::6/64 dev eth1') 41 | lan.expect('ip -6') 42 | lan.expect(prompt) 43 | lan.sendline('ip -6 route add 4aaa::1 dev eth1') 44 | lan.expect(prompt) 45 | lan.sendline('ip -6 route add default via 4aaa::1 dev eth1') 46 | lan.expect(prompt) 47 | if 'No route to host' in lan.before: 48 | raise Exception('Error setting ivp6 routes') 49 | # Wan-side Device 50 | wan.sendline('\nip -6 addr add 5aaa::6/64 dev eth1') 51 | wan.expect('ip -6') 52 | wan.expect(prompt) 53 | wan.sendline('ip -6 route add 5aaa::1 dev eth1') 54 | wan.expect(prompt) 55 | wan.sendline('ip -6 route add default via 5aaa::1 dev eth1') 56 | wan.expect(prompt) 57 | if 'No route to host' in wan.before: 58 | raise Exception('Error setting ivp6 routes') 59 | # Wlan-side Device 60 | if wlan: 61 | wlan.sendline('\nip -6 addr add 4aaa::7/64 dev wlan0') 62 | wlan.expect('ip -6') 63 | wlan.expect(prompt) 64 | wlan.sendline('ip -6 route add 4aaa::1 dev eth1') 65 | wlan.expect(prompt) 66 | wlan.sendline('ip -6 route add default via 4aaa::1 dev wlan0') 67 | wlan.expect(prompt) 68 | # Give things time to get ready 69 | time.sleep(20) 70 | # Check addresses 71 | board.sendline('\nifconfig | grep -B2 addr:') 72 | board.expect('ifconfig ') 73 | board.expect(prompt) 74 | lan.sendline('\nifconfig | grep -B2 addr:') 75 | lan.expect('ifconfig ') 76 | lan.expect(prompt) 77 | wan.sendline('\nifconfig | grep -B2 addr:') 78 | wan.expect('ifconfig ') 79 | wan.expect(prompt) 80 | 81 | 82 | class Remove_IPv6_Addresses(rootfs_boot.RootFSBootTest): 83 | '''Removed IPv6 addresses and default routes for router and devices.''' 84 | def runTest(self): 85 | board.sendline('\nuci delete network.lan.ip6addr') 86 | board.expect('uci ') 87 | board.expect(prompt) 88 | board.sendline('uci delete network.wan.ip6addr') 89 | board.expect(prompt) 90 | board.sendline('ip -6 addr delete 5aaa::1/64 dev eth0') 91 | board.expect(prompt) 92 | board.sendline('uci commit network') 93 | board.expect(prompt) 94 | board.network_restart() 95 | # Lan-side Device 96 | lan.sendline('\nip -6 addr del 4aaa::6/64 dev eth1') 97 | lan.expect('ip -6') 98 | lan.expect(prompt) 99 | lan.sendline('ip -6 route del default') 100 | lan.expect(prompt) 101 | # Wan-side Device 102 | wan.sendline('\nip -6 addr del 5aaa::6/64 dev eth1') 103 | wan.expect('ip -6') 104 | wan.expect(prompt) 105 | wan.sendline('ip -6 route del default') 106 | wan.expect(prompt) 107 | time.sleep(10) 108 | -------------------------------------------------------------------------------- /tests/lib/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | import unittest2 8 | import common 9 | 10 | def expectedFailureIf(test): 11 | def wrap(func): 12 | def wrapped(self, *args, **kwargs): 13 | if test(): 14 | @unittest2.expectedFailure 15 | def f(): func(self) 16 | 17 | return f() 18 | return func(self) 19 | return wrapped 20 | return wrap 21 | -------------------------------------------------------------------------------- /tests/lib/common.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 8 | 9 | import json 10 | import junitxml 11 | import pexpect 12 | import sys 13 | import subprocess 14 | import time 15 | import unittest2 16 | import urllib2 17 | import os 18 | import signal 19 | from termcolor import cprint 20 | 21 | from selenium import webdriver 22 | from selenium.webdriver.common.proxy import * 23 | 24 | ubootprompt = ['ath>', '\(IPQ\) #', 'ar7240>'] 25 | linuxprompt = ['root\\@.*:.*#', '@R7500:/# '] 26 | prompts = ubootprompt + linuxprompt + ['/.* # ', ] 27 | 28 | def run_once(f): 29 | def wrapper(*args, **kwargs): 30 | if not wrapper.has_run: 31 | wrapper.has_run = True 32 | return f(*args, **kwargs) 33 | wrapper.has_run = False 34 | return wrapper 35 | 36 | def spawn_ssh_pexpect(ip, user='root', pw='bigfoot1', prompt=None, port="22", via=None, color=None, o=sys.stdout): 37 | if via: 38 | p = via.sendline("ssh %s@%s -p %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \ 39 | % (user, ip, port)) 40 | p = via 41 | else: 42 | p = pexpect.spawn("ssh %s@%s -p %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \ 43 | % (user, ip, port)) 44 | 45 | i = p.expect(["yes/no", "assword:", "Last login"], timeout=30) 46 | if i == 0: 47 | p.sendline("yes") 48 | i = self.expect(["Last login", "assword:"]) 49 | if i == 1: 50 | p.sendline(pw) 51 | else: 52 | pass 53 | 54 | if prompt is None: 55 | p.prompt = "%s@.*$" % user 56 | else: 57 | p.prompt = prompt 58 | 59 | p.expect(p.prompt) 60 | 61 | from termcolor import colored 62 | class o_helper(): 63 | def __init__(self, color): 64 | self.color = color 65 | def write(self, string): 66 | o.write(colored(string, color)) 67 | def flush(self): 68 | o.flush() 69 | 70 | if color is not None: 71 | p.logfile_read = o_helper(color) 72 | else: 73 | p.logfile_read = o 74 | 75 | return p 76 | 77 | def clear_buffer(console): 78 | try: 79 | console.read_nonblocking(size=2000, timeout=1) 80 | except: 81 | pass 82 | 83 | def phantom_webproxy_driver(ipport): 84 | ''' 85 | Use this if you started web proxy on a machine connected to router's LAN. 86 | ''' 87 | service_args = [ 88 | '--proxy=' + ipport, 89 | '--proxy-type=http', 90 | ] 91 | print("Attempting to setup Phantom.js via proxy %s" % ipport) 92 | driver = webdriver.PhantomJS(service_args=service_args) 93 | driver.set_window_size(1024, 768) 94 | driver.set_page_load_timeout(30) 95 | return driver 96 | 97 | def firefox_webproxy_driver(ipport): 98 | ''' 99 | Use this if you started web proxy on a machine connected to router's LAN. 100 | ''' 101 | proxy = Proxy({ 102 | 'proxyType': 'MANUAL', 103 | 'httpProxy': ipport, 104 | 'ftpProxy': ipport, 105 | 'sslProxy': ipport, 106 | 'noProxy': '' 107 | }) 108 | print("Attempting to open firefox via proxy %s" % ipport) 109 | profile = webdriver.FirefoxProfile() 110 | profile.set_preference('network.http.phishy-userpass-length', 255) 111 | driver = webdriver.Firefox(proxy=proxy, firefox_profile=profile) 112 | caps = webdriver.DesiredCapabilities.FIREFOX 113 | proxy.add_to_capabilities(caps) 114 | #driver = webdriver.Remote(desired_capabilities=caps) 115 | driver.implicitly_wait(30) 116 | driver.set_page_load_timeout(30) 117 | return driver 118 | 119 | def test_msg(msg): 120 | cprint(msg, None, attrs=['bold']) 121 | -------------------------------------------------------------------------------- /tests/lib/installers.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | def apt_install(device, name, timeout=120): 9 | device.sendline('apt-get install -q -y %s' % name) 10 | device.expect('Reading package') 11 | device.expect(device.prompt, timeout=timeout) 12 | 13 | def apt_update(device, timeout=120): 14 | device.sendline('apt-get update') 15 | device.expect('Reading package') 16 | device.expect(device.prompt, timeout=timeout) 17 | 18 | def install_iperf(device): 19 | '''Install iPerf benchmark tool if not present.''' 20 | device.sendline('\niperf -v') 21 | try: 22 | device.expect('iperf version', timeout=10) 23 | device.expect(device.prompt) 24 | except: 25 | device.expect(device.prompt) 26 | device.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install iperf') 27 | device.expect(device.prompt, timeout=60) 28 | 29 | def install_iperf3(device): 30 | '''Install iPerf benchmark tool if not present.''' 31 | device.sendline('\niperf3 -v') 32 | try: 33 | device.expect('iperf 3', timeout=5) 34 | device.expect(device.prompt) 35 | except: 36 | device.expect(device.prompt) 37 | device.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install iperf3') 38 | device.expect(device.prompt, timeout=60) 39 | 40 | def install_lighttpd(device): 41 | '''Install lighttpd web server if not present.''' 42 | device.sendline('\nlighttpd -v') 43 | try: 44 | device.expect('lighttpd/1', timeout=8) 45 | device.expect(device.prompt) 46 | except: 47 | device.expect(device.prompt) 48 | apt_install(device, 'lighttpd') 49 | 50 | def install_netperf(device): 51 | '''Install netperf benchmark tool if not present.''' 52 | device.sendline('\nnetperf -V') 53 | try: 54 | device.expect('Netperf version 2.4', timeout=10) 55 | device.expect(device.prompt) 56 | except: 57 | device.expect(device.prompt) 58 | device.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install netperf') 59 | device.expect(device.prompt, timeout=60) 60 | device.sendline('/etc/init.d/netperf restart') 61 | device.expect('Restarting') 62 | device.expect(device.prompt) 63 | 64 | def install_endpoint(device): 65 | '''Install endpoint if not present.''' 66 | device.sendline('\npgrep endpoint') 67 | try: 68 | device.expect('pgrep endpoint') 69 | device.expect('[0-9]+\r\n', timeout=5) 70 | device.expect(device.prompt) 71 | except: 72 | device.expect(device.prompt) 73 | device.sendline('wget http://downloads.ixiacom.com/products/ixchariot/endpoint_library/8.00/pelinux_amd64_80.tar.gz') 74 | device.expect(device.prompt, timeout=120) 75 | device.sendline('tar xvzf pelinux_amd64_80.tar.gz') 76 | device.expect('endpoint.install', timeout=90) 77 | device.expect(device.prompt, timeout=60) 78 | device.sendline('./endpoint.install accept_license') 79 | device.expect('Installation of endpoint was successful.', timeout=90) 80 | device.expect(device.prompt, timeout=60) 81 | 82 | def install_hping3(device): 83 | '''Install hping3 if not present.''' 84 | device.sendline('\nhping3 --version') 85 | try: 86 | device.expect('hping3 version', timeout=5) 87 | device.expect(device.prompt) 88 | except: 89 | device.expect(device.prompt) 90 | apt_install(device, 'hping3') 91 | -------------------------------------------------------------------------------- /tests/lib/wifi.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 8 | 9 | import random 10 | import re 11 | import string 12 | import time 13 | from devices import prompt 14 | 15 | wlan_iface = None 16 | 17 | def wifi_interface(console): 18 | global wlan_iface 19 | 20 | if wlan_iface is None: 21 | console.sendline('uci show wireless | grep wireless.*0.*type=') 22 | i = console.expect(["type='?mac80211'?", "type='?qcawifi'?"]) 23 | if i == 0: 24 | wlan_iface = "wlan0" 25 | elif i == 1: 26 | wlan_iface = "ath0" 27 | else: 28 | wlan_iface = None 29 | 30 | return wlan_iface 31 | 32 | def randomSSIDName(): 33 | return 'wifi-' + ''.join(random.sample(string.lowercase+string.digits,10)) 34 | 35 | def uciSetWifiSSID(console, ssid): 36 | console.sendline('uci set wireless.@wifi-iface[0].ssid=%s; uci commit wireless; wifi' % ssid) 37 | console.expect(prompt) 38 | 39 | def uciSetWifiMode(console, radio, hwmode): 40 | console.sendline('uci set wireless.wifi%s.hwmode=%s; uci commit wireless' % (radio, hwmode)) 41 | console.expect(prompt) 42 | 43 | def uciSetChannel(console, radio, channel): 44 | console.sendline('uci set wireless.wifi%s.channel=%s; uci commit wireless' % (radio, channel)) 45 | console.expect(prompt) 46 | 47 | def enable_wifi(board, index=0): 48 | board.sendline('\nuci set wireless.@wifi-device[%s].disabled=0; uci commit wireless' % index) 49 | board.expect('uci set') 50 | board.expect(prompt) 51 | board.sendline('wifi') 52 | board.expect('wifi') 53 | board.expect(prompt, timeout=50) 54 | time.sleep(20) 55 | 56 | def enable_all_wifi_interfaces(board): 57 | '''Find all wireless interfaces, and enable them.''' 58 | board.sendline('\nuci show wireless | grep disabled') 59 | board.expect('grep disabled') 60 | board.expect(prompt) 61 | # The following re.findall should return list of settings: 62 | # ['wireless.radio0.disabled', 'wireless.radio1.disabled'] 63 | settings = re.findall('([\w\.]+)=\d', board.before) 64 | for s in settings: 65 | board.sendline('uci set %s=0' % s) 66 | board.expect(prompt) 67 | board.sendline('uci commit wireless') 68 | board.expect(prompt) 69 | board.sendline('wifi') 70 | board.expect(prompt, timeout=50) 71 | 72 | def disable_wifi(board, wlan_iface="ath0"): 73 | board.sendline('uci set wireless.@wifi-device[0].disabled=1; uci commit wireless') 74 | board.expect('uci set') 75 | board.expect(prompt) 76 | board.sendline('wifi') 77 | board.expect(prompt) 78 | board.sendline('iwconfig %s' % wlan_iface) 79 | board.expect(prompt) 80 | 81 | def wifi_on(board): 82 | '''Return True if WiFi is enabled.''' 83 | board.sendline('\nuci show wireless.@wifi-device[0].disabled') 84 | try: 85 | board.expect('disabled=0', timeout=5) 86 | board.expect(prompt) 87 | return True 88 | except: 89 | return False 90 | 91 | def wifi_get_info(board, wlan_iface): 92 | try: 93 | if "ath" in wlan_iface: 94 | board.sendline('iwconfig %s' % wlan_iface) 95 | board.expect('ESSID:"(.*)"') 96 | essid = board.match.group(1) 97 | board.expect("Frequency:([^ ]+)") 98 | freq = board.match.group(1) 99 | essid = board.match.group(1) 100 | board.expect('Bit Rate[:=]([^ ]+) ') 101 | rate = float(board.match.group(1)) 102 | board.expect(prompt) 103 | # TODO: determine channel 104 | channel = -1.0 105 | elif "wlan" in wlan_iface: 106 | board.sendline("iwinfo wlan0 info") 107 | board.expect('ESSID: "(.*)"') 108 | essid = board.match.group(1) 109 | board.expect('Channel:\s*(\d+)\s*\(([\d\.]+)\s*GHz') 110 | channel = int(board.match.group(1)) 111 | freq = float(board.match.group(2)) 112 | board.expect('Bit Rate: ([^ ]+)') 113 | try: 114 | rate = float(board.match.group(1)) 115 | except: 116 | rate = -1.0 117 | board.expect(prompt) 118 | else: 119 | print("Unknown wireless type") 120 | except: 121 | board.sendline('dmesg') 122 | board.expect(prompt) 123 | raise 124 | 125 | return essid, channel, rate, freq 126 | 127 | def wait_wifi_up(board, num_tries=10, sleep=15, wlan_iface="ath0"): 128 | '''Wait for WiFi Bit Rate to be != 0.''' 129 | for i in range(num_tries): 130 | time.sleep(sleep) 131 | essid, channel, rate, freq = wifi_get_info(board, wlan_iface) 132 | if "ath" in wlan_iface and rate > 0: 133 | return 134 | if "wlan" in wlan_iface == "wlan0" and essid != "" and channel != 0 and freq != 0.0: 135 | return 136 | 137 | if rate == 0: 138 | print("\nWiFi did not come up. Bit Rate still 0.") 139 | assert False 140 | 141 | def wifi_add_vap(console, phy, ssid): 142 | console.sendline('uci add wireless wifi-iface') 143 | console.expect(prompt) 144 | console.sendline('uci set wireless.@wifi-iface[-1].device="%s"' % phy) 145 | console.expect(prompt) 146 | console.sendline('uci set wireless.@wifi-iface[-1].network="lan"') 147 | console.expect(prompt) 148 | console.sendline('uci set wireless.@wifi-iface[-1].mode="ap"') 149 | console.expect(prompt) 150 | console.sendline('uci set wireless.@wifi-iface[-1].ssid="%s"' % ssid) 151 | console.expect(prompt) 152 | console.sendline('uci set wireless.@wifi-iface[-1].encryption="none"') 153 | console.expect(prompt) 154 | console.sendline('uci commit') 155 | console.expect(prompt) 156 | 157 | def wifi_del_vap(console, index): 158 | console.sendline('uci delete wireless.@wifi-iface[%s]' % index) 159 | console.expect(prompt) 160 | console.sendline('uci commit') 161 | console.expect(prompt) 162 | 163 | def uciSetWifiSecurity(board, vap_iface, security): 164 | if security.lower() in ['none']: 165 | print("Setting security to none.") 166 | board.sendline('uci set wireless.@wifi-iface[%s].encryption=none' % vap_iface) 167 | board.expect(prompt) 168 | elif security.lower() in ['wpa-psk']: 169 | print("Setting security to WPA-PSK.") 170 | board.sendline('uci set wireless.@wifi-iface[%s].encryption=psk+tkip' % vap_iface) 171 | board.expect(prompt) 172 | board.sendline('uci set wireless.@wifi-iface[%s].key=1234567890abcdexyz' % vap_iface) 173 | board.expect(prompt) 174 | elif security.lower() in ['wpa2-psk']: 175 | print("Setting security to WPA2-PSK.") 176 | board.sendline('uci set wireless.@wifi-iface[%s].encryption=psk2+ccmp' % vap_iface) 177 | board.expect(prompt) 178 | board.sendline('uci set wireless.@wifi-iface[%s].key=1234567890abcdexyz' % vap_iface) 179 | board.expect(prompt) 180 | -------------------------------------------------------------------------------- /tests/linux_boot.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import time 9 | import unittest2 10 | import lib 11 | import sys 12 | import traceback 13 | 14 | from devices import board, wan, lan, wlan, prompt 15 | 16 | class LinuxBootTest(unittest2.TestCase): 17 | 18 | def __init__(self, config): 19 | super(LinuxBootTest, self).__init__("testWrapper") 20 | self.config = config 21 | self.reset_after_fail = True 22 | self.dont_retry = False 23 | self.logged = dict() 24 | 25 | def id(self): 26 | return self.__class__.__name__ 27 | 28 | def setUp(self): 29 | lib.common.test_msg("\n==================== Begin %s ====================" % self.__class__.__name__) 30 | def tearDown(self): 31 | lib.common.test_msg("\n==================== End %s ======================" % self.__class__.__name__) 32 | 33 | def wan_setup(self): 34 | None 35 | 36 | def lan_setup(self): 37 | None 38 | 39 | def wlan_setup(self): 40 | None 41 | 42 | def wan_cleanup(self): 43 | None 44 | 45 | def lan_cleanup(self): 46 | None 47 | 48 | def wlan_cleanup(self): 49 | None 50 | 51 | def testWrapper(self): 52 | if not board.isalive(): 53 | self.result_grade = "SKIP" 54 | self.skipTest("Board is not alive") 55 | raise 56 | 57 | try: 58 | if wan and hasattr(self, 'wan_setup'): 59 | self.wan_setup() 60 | if lan and hasattr(self, 'lan_setup'): 61 | self.lan_setup() 62 | if wlan and hasattr(self, 'wlan_setup'): 63 | self.wlan_setup() 64 | 65 | if self.config.retry and not self.dont_retry: 66 | retry = self.config.retry 67 | else: 68 | retry = 0 69 | 70 | while retry >= 0: 71 | try: 72 | self.runTest() 73 | retry = -1 74 | except Exception as e: 75 | retry = retry - 1 76 | if(retry > 0): 77 | print(e.get_trace()) 78 | print("\n\n----------- Test failed! Retrying in 5 seconds... -------------") 79 | time.sleep(5) 80 | else: 81 | raise 82 | 83 | if wan and hasattr(self, 'wan_cleanup'): 84 | self.wan_cleanup() 85 | if lan and hasattr(self, 'lan_cleanup'): 86 | self.lan_cleanup() 87 | if wlan and hasattr(self, 'wlan_cleanup'): 88 | self.wlan_cleanup() 89 | 90 | if hasattr(self, 'expected_failure') and self.expected_failure: 91 | self.result_grade = "Unexp OK" 92 | else: 93 | self.result_grade = "OK" 94 | except unittest2.case.SkipTest: 95 | self.result_grade = "SKIP" 96 | print("\n\n=========== Test skipped! Moving on... =============") 97 | raise 98 | except Exception as e: 99 | if hasattr(self, 'expected_failure') and self.expected_failure: 100 | self.result_grade = "Exp FAIL" 101 | else: 102 | self.result_grade = "FAIL" 103 | print("\n\n=========== Test failed! Running recovery ===========") 104 | if e.__class__.__name__ == "TIMEOUT": 105 | print(e.get_trace()) 106 | else: 107 | print(e) 108 | traceback.print_exc(file=sys.stdout) 109 | self.recover() 110 | raise 111 | 112 | def recover(self): 113 | if self.__class__.__name__ == "LinuxBootTest": 114 | print("aborting tests, unable to boot..") 115 | sys.exit(1) 116 | print("ERROR: No default recovery!") 117 | raise "No default recovery!" 118 | -------------------------------------------------------------------------------- /tests/lsmod.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import re 9 | import rootfs_boot 10 | from devices import board, wan, lan, wlan, prompt 11 | 12 | class KernelModules(rootfs_boot.RootFSBootTest): 13 | '''lsmod shows loaded kernel modules.''' 14 | def runTest(self): 15 | board.check_output('lsmod | wc -l') 16 | tmp = re.search('\d+', board.before) 17 | num = int(tmp.group(0)) - 1 # subtract header line 18 | board.check_output('lsmod | sort') 19 | self.result_message = '%s kernel modules are loaded.' % num 20 | self.logged['num_loaded'] = num 21 | -------------------------------------------------------------------------------- /tests/netperf_bidir.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import unittest2 9 | import lib 10 | import netperf_reverse_test 11 | import pexpect 12 | import sys 13 | import time 14 | 15 | from devices import board, wan, lan, wlan, prompt 16 | 17 | class NetperfBidirTest(netperf_reverse_test.NetperfReverseTest): 18 | def runTest(self): 19 | board.sendline('mpstat -P ALL 30 1') 20 | opts = "" 21 | num_conns = 1 22 | 23 | up = down = 0.0 24 | for i in range(0, num_conns): 25 | self.run_netperf_cmd(lan, "192.168.0.1 -c -C -l 30 -- %s" % opts) 26 | self.run_netperf_cmd(lan, "192.168.0.1 -c -C -l 30 -t TCP_MAERTS -- %s" % opts) 27 | 28 | for i in range(0, num_conns): 29 | up += float(self.run_netperf_parse(lan)) 30 | down += float(self.run_netperf_parse(lan)) 31 | 32 | board.expect('Average.*idle\r\nAverage:\s+all(\s+[0-9]+.[0-9]+){10}\r\n') 33 | idle_cpu = float(board.match.group(1)) 34 | avg_cpu = 100 - float(idle_cpu) 35 | 36 | msg = "Bidir speed was %s 10^6Mbits/sec with %s average cpu" % ((up + down), avg_cpu) 37 | lib.common.test_msg(msg) 38 | self.result_message = msg 39 | -------------------------------------------------------------------------------- /tests/netperf_reverse_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import unittest2 9 | import lib 10 | import netperf_test 11 | import pexpect 12 | import sys 13 | import time 14 | 15 | from devices import board, wan, lan, wlan, prompt 16 | 17 | class NetperfReverseTest(netperf_test.NetperfTest): 18 | '''Setup Netperf and Ran Reverse Throughput.''' 19 | def runTest(self): 20 | # setup port forwarding to lan netperf server 21 | lan_priv_ip = lan.get_interface_ipaddr("eth1") 22 | board.uci_forward_traffic_redirect("tcp", "12865", lan_priv_ip) 23 | # setup port for data socket separate from control port 24 | board.uci_forward_traffic_redirect("tcp", "12866", lan_priv_ip) 25 | 26 | wan_ip = board.get_interface_ipaddr(board.wan_iface) 27 | 28 | # send at router ip, which will forward to lan client 29 | wan.sendline('') 30 | wan.expect(prompt) 31 | board.sendline('mpstat -P ALL 30 1') 32 | speed = self.run_netperf(wan, wan_ip, "-c -C -l 30 -t TCP_STREAM -- -P ,12866") 33 | board.expect('Average.*idle\r\nAverage:\s+all(\s+[0-9]+.[0-9]+){10}\r\n') 34 | idle_cpu = float(board.match.group(1)) 35 | avg_cpu = 100 - float(idle_cpu) 36 | lib.common.test_msg("Average cpu usage was %s" % avg_cpu) 37 | 38 | self.result_message = "Setup NetperfReverse and Ran Throughput (Speed = %s 10^6bits/sec, CPU = %s)" % (speed, avg_cpu) 39 | -------------------------------------------------------------------------------- /tests/netperf_rfc2544.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | from devices import board, wan, lan, wlan, prompt 10 | 11 | class NetperfRFC2544(rootfs_boot.RootFSBootTest): 12 | '''Single test to simulate RFC2544''' 13 | def runTest(self): 14 | for sz in ["74", "128", "256", "512", "1024", "1280", "1518" ]: 15 | print("running %s UDP test" % sz) 16 | lan.sendline('netperf -H 192.168.0.1 -t UDP_STREAM -l 60 -- -m %s' % sz) 17 | lan.expect_exact('netperf -H 192.168.0.1 -t UDP_STREAM -l 60 -- -m %s' % sz) 18 | lan.expect('UDP UNIDIRECTIONAL') 19 | lan.expect(prompt, timeout=90) 20 | board.sendline() 21 | board.expect(prompt) 22 | -------------------------------------------------------------------------------- /tests/netperf_stress_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import unittest2 9 | import lib 10 | import netperf_test 11 | import pexpect 12 | import sys 13 | import time 14 | 15 | from netperf_test import install_netperf 16 | from devices import board, wan, lan, wlan, prompt 17 | 18 | class NetperfStressTest(netperf_test.NetperfTest): 19 | @lib.common.run_once 20 | def wan_setup(self): 21 | install_netperf(wan) 22 | @lib.common.run_once 23 | def lan_setup(self): 24 | install_netperf(lan) 25 | 26 | def runTest(self): 27 | # Record number of bytes and packets sent through interfaces 28 | board.sendline("\nifconfig | grep 'encap\|packets\|bytes'") 29 | board.expect('br-lan') 30 | board.expect(prompt) 31 | 32 | # Start netperf tests 33 | num_conn = 200 34 | run_time = 30 35 | pkt_size = 256 36 | 37 | board.sendline('mpstat -P ALL %s 1' % run_time) 38 | print("\nStarting %s netperf tests in parallel." % num_conn) 39 | opts = '192.168.0.1 -c -C -l %s -- -m %s -M %s -D' % (run_time, pkt_size, pkt_size) 40 | for i in range(0, num_conn): 41 | self.run_netperf_cmd_nowait(lan, opts) 42 | # Let netperf tests run 43 | time.sleep(run_time*1.5) 44 | 45 | board.expect('Average:\s+all.*\s+([0-9]+.[0-9]+)\r\n') 46 | idle_cpu = board.match.group(1) 47 | avg_cpu = 100 - float(idle_cpu) 48 | print("Average cpu usage was %s" % avg_cpu) 49 | 50 | # try to flush out backlog of buffer from above, we try b/c not all might start 51 | # correctly 52 | try: 53 | for i in range(0, num_conn): 54 | lan.exepct('TEST') 55 | except: 56 | pass 57 | 58 | # add up as many netperf connections results that were established 59 | try: 60 | bandwidth = 0.0 61 | conns_parsed = 0 62 | for i in range(0, num_conn): 63 | bandwidth += self.run_netperf_parse(lan, timeout=1) * run_time 64 | conns_parsed += 1 65 | except Exception as e: 66 | # print the exception for logging reasons 67 | print(e) 68 | pass 69 | 70 | # make sure at least one netperf was run 71 | assert (conns_parsed > 0) 72 | 73 | board.sendline('pgrep logger | wc -l') 74 | board.expect('([0-9]+)\r\n') 75 | n = board.match.group(1) 76 | 77 | print("Stopped with %s connections, %s netperf's still running" % (conns_parsed, n)) 78 | print("Mbits passed was %s" % bandwidth) 79 | 80 | # Record number of bytes and packets sent through interfaces 81 | board.sendline("ifconfig | grep 'encap\|packets\|bytes'") 82 | board.expect('br-lan') 83 | board.expect(prompt) 84 | 85 | lan.sendline('killall netperf') 86 | lan.expect(prompt) 87 | lan.sendline("") 88 | lan.expect(prompt) 89 | lib.common.clear_buffer(lan) 90 | 91 | self.result_message = "Ran %s/%s for %s seconds (Pkt Size = %s, Mbits = %s, CPU = %s)" \ 92 | % (conns_parsed, num_conn, run_time, pkt_size, bandwidth, avg_cpu) 93 | -------------------------------------------------------------------------------- /tests/netperf_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import unittest2 9 | import lib 10 | import rootfs_boot 11 | import pexpect 12 | import sys 13 | import time 14 | import os 15 | from devices import board, wan, lan, wlan, prompt 16 | 17 | def install_netperf(device): 18 | # Check version 19 | device.sendline('\nnetperf -V') 20 | try: 21 | device.expect('Netperf version 2.4', timeout=10) 22 | device.expect(device.prompt) 23 | except: 24 | # Install Netperf 25 | device.sendline('apt-get update') 26 | device.expect(device.prompt, timeout=60) 27 | device.sendline('apt-get -o DPkg::Options::="--force-confnew" -y --force-yes install netperf') 28 | device.expect(device.prompt, timeout=60) 29 | device.sendline('/etc/init.d/netperf restart') 30 | device.expect('Restarting') 31 | device.expect(device.prompt) 32 | 33 | class NetperfTest(rootfs_boot.RootFSBootTest): 34 | '''Setup Netperf and Ran Throughput.''' 35 | @lib.common.run_once 36 | def lan_setup(self): 37 | super(NetperfTest, self).lan_setup() 38 | install_netperf(lan) 39 | @lib.common.run_once 40 | def wan_setup(self): 41 | super(NetperfTest, self).wan_setup() 42 | install_netperf(wan) 43 | def recover(self): 44 | lan.sendcontrol('c') 45 | 46 | # if you are spawning a lot of connections, sometimes it 47 | # takes too long to wait for the connection to be established 48 | # so we use this version 49 | def run_netperf_cmd_nowait(self, device, ip, opts="", quiet=False): 50 | device.sendline('netperf -H %s %s &' % (ip, opts)) 51 | 52 | def run_netperf_cmd(self, device, ip, opts="", quiet=False): 53 | if quiet: 54 | device.sendline('netperf -H %s %s > /dev/null &' % (ip, opts)) 55 | else: 56 | device.sendline('netperf -H %s %s &' % (ip, opts)) 57 | device.expect('TEST.*\r\n') 58 | 59 | def run_netperf_parse(self, device, timeout=60): 60 | device.expect('[0-9]+\s+[0-9]+\s+[0-9]+\s+[0-9]+.[0-9]+\s+([0-9]+.[0-9]+)', timeout=timeout) 61 | ret = device.match.group(1) 62 | lib.common.test_msg("Speed was %s 10^6bits/sec" % ret) 63 | return float(ret) 64 | 65 | def run_netperf(self, device, ip, opts="", timeout=60): 66 | self.run_netperf_cmd(device, ip, opts=opts) 67 | return self.run_netperf_parse(device) 68 | 69 | def runTest(self): 70 | super(NetperfTest, self).runTest() 71 | 72 | board.sendline('mpstat -P ALL 30 1') 73 | speed = self.run_netperf(lan, "192.168.0.1 -c -C -l 30") 74 | board.expect('Average.*idle\r\nAverage:\s+all(\s+[0-9]+.[0-9]+){10}\r\n') 75 | idle_cpu = float(board.match.group(1)) 76 | avg_cpu = 100 - float(idle_cpu) 77 | lib.common.test_msg("Average cpu usage was %s" % avg_cpu) 78 | 79 | self.result_message = "Setup Netperf and Ran Throughput (Speed = %s 10^6bits/sec, CPU = %s)" % (speed, avg_cpu) 80 | 81 | def run_netperf_tcp(device, run_time, pkt_size, direction="up"): 82 | if direction == "up": 83 | cmd = "netperf -H 192.168.0.1 -c -C -l %s -- -m %s -M %s -D" % (run_time, pkt_size, pkt_size) 84 | else: 85 | cmd = "netperf -H 192.168.0.1 -c -C -l %s -t TCP_MAERTS -- -m %s -M %s -D" % (run_time, pkt_size, pkt_size) 86 | device.sendline(cmd) 87 | device.expect('TEST.*\r\n') 88 | device.expect(device.prompt, timeout=run_time+4) 89 | 90 | class Netperf_UpTCP256(rootfs_boot.RootFSBootTest): 91 | '''Netperf upload throughput (TCP, packets size 256 Bytes).''' 92 | def runTest(self): 93 | run_netperf_tcp(device=lan, run_time=15, pkt_size=256) 94 | 95 | class Netperf_UpTCP512(rootfs_boot.RootFSBootTest): 96 | '''Netperf upload throughput (TCP, packets size 512 Bytes).''' 97 | def runTest(self): 98 | run_netperf_tcp(device=lan, run_time=15, pkt_size=512) 99 | 100 | class Netperf_UpTCP1024(rootfs_boot.RootFSBootTest): 101 | '''Netperf upload throughput (TCP, packets size 1024 Bytes).''' 102 | def runTest(self): 103 | run_netperf_tcp(device=lan, run_time=15, pkt_size=1024) 104 | 105 | class Netperf_DownTCP256(rootfs_boot.RootFSBootTest): 106 | '''Netperf download throughput (TCP, packets size 256 Bytes).''' 107 | def runTest(self): 108 | run_netperf_tcp(device=lan, run_time=15, pkt_size=256, direction="down") 109 | 110 | class Netperf_DownTCP512(rootfs_boot.RootFSBootTest): 111 | '''Netperf download throughput (TCP, packets size 512 Bytes).''' 112 | def runTest(self): 113 | run_netperf_tcp(device=lan, run_time=15, pkt_size=512, direction="down") 114 | 115 | class Netperf_DownTCP1024(rootfs_boot.RootFSBootTest): 116 | '''Netperf download throughput (TCP, packets size 1024 Bytes).''' 117 | def runTest(self): 118 | run_netperf_tcp(device=lan, run_time=15, pkt_size=1024, direction="down") 119 | -------------------------------------------------------------------------------- /tests/netperf_udp_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import unittest2 9 | import lib 10 | import netperf_test 11 | import pexpect 12 | import sys 13 | import time 14 | 15 | from devices import board, wan, lan, wlan, prompt 16 | 17 | class NetperfUdpTest(netperf_test.NetperfTest): 18 | @lib.common.run_once 19 | def runTest(self): 20 | super(NetperfUdpTest, self).runTest() 21 | 22 | self.run_netperf(lan, "192.168.0.1 -c -C -l 30 -t UDP_STREAM -- -m 1460 -M 1460") 23 | 24 | # setup port forwarding to lan netperf server 25 | lan_priv_ip = lan.get_interface_ipaddr("eth1") 26 | board.uci_forward_traffic_redirect("tcp", "12865", lan_priv_ip) 27 | # setup port for data socket separate from control port 28 | board.uci_forward_traffic_redirect("udp", "12866", lan_priv_ip) 29 | 30 | wan_ip = board.get_interface_ipaddr(board.wan_iface) 31 | 32 | # send at router ip, which will forward to lan client 33 | wan.sendline('') 34 | wan.expect(prompt) 35 | self.run_netperf(wan, wan_ip, "-c -C -l 30 -t UDP_STREAM -- -P ,12866 -m 1460 -M 1460") 36 | -------------------------------------------------------------------------------- /tests/network_restart.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import time 9 | 10 | import rootfs_boot 11 | from devices import board, wan, lan, wlan, prompt 12 | 13 | class RestartNetwork(rootfs_boot.RootFSBootTest): 14 | '''Restarted router network.''' 15 | def runTest(self): 16 | board.network_restart() 17 | print("\nWaiting 30s to give things time to fully start...\n") 18 | time.sleep(30) 19 | def recover(self): 20 | board.sendcontrol('c') 21 | -------------------------------------------------------------------------------- /tests/nmap.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import random 9 | import re 10 | 11 | import rootfs_boot 12 | from devices import board, wan, lan, wlan, prompt 13 | 14 | class Nmap_LAN(rootfs_boot.RootFSBootTest): 15 | '''Ran nmap port scanning tool on LAN interface.''' 16 | def recover(self): 17 | lan.sendcontrol('c') 18 | def runTest(self): 19 | lan.sendline('nmap -sS -A -v -p 1-10000 192.168.1.1') 20 | lan.expect('Starting Nmap') 21 | lan.expect('Nmap scan report', timeout=660) 22 | lan.expect(prompt, timeout=60) 23 | open_ports = re.findall("(\d+)/tcp\s+open", lan.before) 24 | msg = "Found %s open TCP ports on LAN interface: %s." % \ 25 | (len(open_ports), ", ".join(open_ports)) 26 | self.result_message = msg 27 | 28 | class Nmap_WAN(rootfs_boot.RootFSBootTest): 29 | '''Ran nmap port scanning tool on WAN interface.''' 30 | def recover(self): 31 | wan.sendcontrol('c') 32 | def runTest(self): 33 | wan_ip_addr = board.get_interface_ipaddr('eth0') 34 | wan.sendline('\nnmap -sS -A -v %s' % wan_ip_addr) 35 | wan.expect('Starting Nmap', timeout=5) 36 | wan.expect('Nmap scan report', timeout=120) 37 | wan.expect(prompt, timeout=60) 38 | open_ports = re.findall("(\d+)/tcp\s+open", wan.before) 39 | msg = "Found %s open TCP ports on WAN interface." % len(open_ports) 40 | self.result_message = msg 41 | assert len(open_ports) == 0 42 | 43 | class UDP_Stress(rootfs_boot.RootFSBootTest): 44 | '''Ran nmap through router, creating hundreds of UDP connections.''' 45 | def runTest(self): 46 | start_port = random.randint(1, 11000) 47 | lan.sendline('\nnmap --min-rate 100 -sU -p %s-%s 192.168.0.1' % (start_port, start_port+200)) 48 | lan.expect('Starting Nmap', timeout=5) 49 | lan.expect('Nmap scan report', timeout=30) 50 | lan.expect(prompt) 51 | def recover(self): 52 | lan.sendcontrol('c') 53 | -------------------------------------------------------------------------------- /tests/openwrt_version.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import re 9 | import rootfs_boot 10 | from devices import board, wan, lan, wlan, prompt 11 | 12 | class OpenwrtVersion(rootfs_boot.RootFSBootTest): 13 | '''Openwrt release file exists and contains expected data.''' 14 | def runTest(self): 15 | board.check_output('cat /etc/openwrt_release', timeout=6) 16 | info = dict(re.findall('DISTRIB_([a-zA-Z]+)=[\'"]([^"\']+)[\'"]', board.before)) 17 | self.result_message = 'Openwrt release is "%(RELEASE)s", revision "%(REVISION)s", and codename "%(CODENAME)s".' % info 18 | self.logged['rev'] = info['REVISION'] 19 | self.logged['name'] = info['CODENAME'] 20 | -------------------------------------------------------------------------------- /tests/opkg.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import time 9 | 10 | import rootfs_boot 11 | from devices import board, wan, lan, wlan, prompt 12 | 13 | class OpkgList(rootfs_boot.RootFSBootTest): 14 | '''Opkg list shows installed packages.''' 15 | def runTest(self): 16 | board.sendline('\nopkg list-installed | wc -l') 17 | board.expect('opkg list') 18 | board.expect('(\d+)\r\n') 19 | num_pkgs = int(board.match.group(1)) 20 | board.expect(prompt) 21 | board.sendline('opkg list-installed') 22 | board.expect(prompt) 23 | self.result_message = '%s OpenWrt packages are installed.' % num_pkgs 24 | self.logged['num_installed'] = num_pkgs 25 | 26 | class CheckQosScripts(rootfs_boot.RootFSBootTest): 27 | '''Package "qos-scripts" is not installed.''' 28 | def runTest(self): 29 | board.sendline('\nopkg list | grep qos-scripts') 30 | try: 31 | board.expect('qos-scripts - ', timeout=4) 32 | except: 33 | return # pass if not installed 34 | assert False # fail if installed 35 | 36 | class OpkgUpdate(rootfs_boot.RootFSBootTest): 37 | '''Opkg is able to update list of packages.''' 38 | def runTest(self): 39 | board.sendline('\nopkg update && echo "All package lists updated"') 40 | board.expect('Updated list of available packages') 41 | board.expect('All package lists updated') 42 | board.expect(prompt) 43 | 44 | -------------------------------------------------------------------------------- /tests/opkg_conf.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import time 9 | 10 | import rootfs_boot 11 | from devices import board, wan, lan, wlan, prompt 12 | 13 | class OpkgConfUpdateMD5(rootfs_boot.RootFSBootTest): 14 | '''Check that opkg will overwrite old configuration files with known MD5.''' 15 | def runTest(self): 16 | board.sendline('\ncp /etc/config/ulogd /etc/config/ulogd.bak') 17 | board.expect(prompt) 18 | board.sendline('echo boardfarmteststring > /etc/config/ulogd') 19 | board.expect(prompt) 20 | board.sendline('touch -r /etc/config/ulogd.bak /etc/config/ulogd') 21 | board.expect(prompt) 22 | board.sendline('sed -i "s|/etc/config/ulogd .*|/etc/config/ulogd 48b1215c8d419a33818fc1f42c118aed|" /usr/lib/opkg/status') 23 | board.expect(prompt) 24 | board.sendline('opkg install --force-reinstall ulogd') 25 | board.expect(prompt) 26 | board.sendline('grep boardfarmteststring /etc/config/ulogd || uname') 27 | board.expect('Linux') 28 | board.sendline('\nmv /etc/config/ulogd.bak /etc/config/ulogd') 29 | board.expect(prompt) 30 | def recover(self): 31 | board.sendline('\nmv /etc/config/ulogd.bak /etc/config/ulogd') 32 | board.expect(prompt) 33 | 34 | class OpkgConfNotUpdateMD5(rootfs_boot.RootFSBootTest): 35 | '''Check that opkg will not overwrite old modified configuration files with known MD5.''' 36 | def runTest(self): 37 | board.sendline('\ncp /etc/config/ulogd /etc/config/ulogd.bak') 38 | board.expect(prompt) 39 | board.sendline('echo boardfarmteststring > /etc/config/ulogd') 40 | board.expect(prompt) 41 | board.sendline('touch -r /etc/config/ulogd.bak /etc/config/ulogd') 42 | board.expect(prompt) 43 | board.sendline('sed -i "s|/etc/config/ulogd .*|/etc/config/ulogd aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|" /usr/lib/opkg/status') 44 | board.expect(prompt) 45 | board.sendline('opkg install --force-reinstall ulogd') 46 | board.expect('Existing conffile /etc/config/ulogd is different from the conffile in the new package') 47 | board.expect(prompt) 48 | board.sendline('grep boardfarmteststring /etc/config/ulogd && uname') 49 | board.expect('Linux') 50 | board.sendline('\nmv /etc/config/ulogd.bak /etc/config/ulogd') 51 | board.expect(prompt) 52 | def recover(self): 53 | board.sendline('\nmv /etc/config/ulogd.bak /etc/config/ulogd') 54 | board.expect(prompt) 55 | 56 | class OpkgConfUpdateSHA256(rootfs_boot.RootFSBootTest): 57 | '''Check that opkg will overwrite old configuration files with known MD5.''' 58 | def runTest(self): 59 | board.sendline('\ncp /etc/config/ulogd /etc/config/ulogd.bak') 60 | board.expect(prompt) 61 | board.sendline('echo boardfarmteststring > /etc/config/ulogd') 62 | board.expect(prompt) 63 | board.sendline('touch -r /etc/config/ulogd.bak /etc/config/ulogd') 64 | board.expect(prompt) 65 | board.sendline('sed -i "s|/etc/config/ulogd .*|/etc/config/ulogd 4212ae0a86f553b7aac741a734a0b973193a9fbe179b28a5d8c2a50cc51e25f0|" /usr/lib/opkg/status') 66 | board.expect(prompt) 67 | board.sendline('opkg install --force-reinstall ulogd') 68 | board.expect(prompt) 69 | board.sendline('grep boardfarmteststring /etc/config/ulogd || uname') 70 | board.expect('Linux') 71 | board.sendline('\nmv /etc/config/ulogd.bak /etc/config/ulogd') 72 | board.expect(prompt) 73 | def recover(self): 74 | board.sendline('\nmv /etc/config/ulogd.bak /etc/config/ulogd') 75 | board.expect(prompt) 76 | 77 | class OpkgConfNotUpdateSHA256(rootfs_boot.RootFSBootTest): 78 | '''Check that opkg will not overwrite old modified configuration files with known MD5.''' 79 | def runTest(self): 80 | board.sendline('\ncp /etc/config/ulogd /etc/config/ulogd.bak') 81 | board.expect(prompt) 82 | board.sendline('echo boardfarmteststring > /etc/config/ulogd') 83 | board.expect(prompt) 84 | board.sendline('touch -r /etc/config/ulogd.bak /etc/config/ulogd') 85 | board.expect(prompt) 86 | board.sendline('sed -i "s|/etc/config/ulogd .*|/etc/config/ulogd aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|" /usr/lib/opkg/status') 87 | board.expect(prompt) 88 | board.sendline('opkg install --force-reinstall ulogd') 89 | board.expect('Existing conffile /etc/config/ulogd is different from the conffile in the new package') 90 | board.expect(prompt) 91 | board.sendline('grep boardfarmteststring /etc/config/ulogd && uname') 92 | board.expect('Linux') 93 | board.sendline('\nmv /etc/config/ulogd.bak /etc/config/ulogd') 94 | board.expect(prompt) 95 | def recover(self): 96 | board.sendline('\nmv /etc/config/ulogd.bak /etc/config/ulogd') 97 | board.expect(prompt) 98 | 99 | -------------------------------------------------------------------------------- /tests/perfperpkt_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import unittest2 9 | import lib 10 | import iperf_test 11 | import pexpect 12 | import sys 13 | import time 14 | 15 | from devices import board, wan, lan, wlan, prompt 16 | 17 | class PerfPerPktTest(iperf_test.iPerfTest): 18 | '''Count various perf events on a per packet basis''' 19 | 20 | def extra(self, perf_parse): 21 | # calculate abstract IPC 22 | for p in perf_parse: 23 | if p['name'] == "instructions": 24 | insn = p['value'] 25 | if p['name'] == "cycles": 26 | cyc = p['value'] 27 | 28 | ipc = float(insn) / cyc 29 | self.logged['ipc'] = ipc 30 | return ", IPC=%.2f" % ipc 31 | 32 | def perf_events(self): 33 | return ["cycles", 34 | "instructions", 35 | "dcache_misses", 36 | "icache_misses"] 37 | 38 | def runTest(self, client=lan, client_name="br-lan"): 39 | if not board.check_perf(): 40 | self.result_message = 'perf not in image. skipping test.' 41 | self.skipTest('perf not installed, skipping test') 42 | 43 | wan_iface = board.get_wan_iface() 44 | 45 | # TODO: remove these and run separate tests 46 | board.sendline('streamboost disable') 47 | board.expect(prompt) 48 | board.sendline('rmmod ecm') 49 | board.expect(prompt) 50 | 51 | self.pkt_size = 200 52 | self.conns = 5 53 | self.test_time = 60 54 | 55 | # work around so we can call from connect testsuite (without reboot) 56 | board.get_wan_iface() 57 | 58 | self.run_iperf_server(wan) 59 | self.run_iperf(client, opts="-t %s -P %s -N -m -M %s" % (self.test_time+10, self.conns, self.pkt_size)) 60 | 61 | # run perf wrapper command 62 | board.check_output_perf("sar -u -n DEV 100000 1", self.perf_events()) 63 | 64 | speed = self.parse_iperf(client, connections=self.conns, t=self.test_time) 65 | self.kill_iperf(wan) 66 | lib.common.test_msg("\n speed was %s Mbit/s" % speed) 67 | 68 | # extract cpu and packet info 69 | board.sendcontrol('c') 70 | idle, wan_pps, client_pps = board.parse_sar_iface_pkts(wan_iface, client_name) 71 | lib.common.test_msg("\n idle cpu: %s" % idle) 72 | lib.common.test_msg("client pps = %s" % client_pps) 73 | lib.common.test_msg("wan pps = %s" % wan_pps) 74 | 75 | if client_name is not None: 76 | total_pps = min(wan_pps, client_pps) 77 | else: 78 | total_pps = wan_pps 79 | lib.common.test_msg("\n using total pps = %s" % total_pps) 80 | 81 | wan_pkts = wan_pps * self.test_time 82 | 83 | if client_name is not None: 84 | client_pkts = client_pps * self.test_time 85 | else: 86 | client_pkts = 'n/a' 87 | 88 | lib.common.test_msg("\n client pkts = %s wan pkts = %s" % (client_pkts, wan_pkts)) 89 | 90 | if client_name is not None: 91 | total_pkts = min(client_pkts, wan_pkts) 92 | else: 93 | total_pkts = wan_pkts 94 | 95 | lib.common.test_msg("\n using total packets = %s" % total_pkts) 96 | self.logged['total_pkts'] = total_pkts 97 | 98 | # extract perf info 99 | perf_msg = "" 100 | 101 | results = board.parse_perf(self.perf_events()) 102 | for p in results: 103 | p['value_per_pkt'] = p['value'] / total_pkts 104 | lib.common.test_msg("\n %s = %s (per pkt = %s)" % \ 105 | (p['name'], p['value'], p['value_per_pkt'])) 106 | perf_msg += ", %s=%.2f" % (p['sname'], p['value_per_pkt']) 107 | 108 | # restore legacy names 109 | if p['name'] == "instructions": 110 | name = "insn_per_pkt" 111 | elif p['name'] == "cycles": 112 | name = "cycles_per_pkt" 113 | elif p['name'] == "dcache_misses": 114 | name = "dcache_miss_per_pkt" 115 | elif p['name'] == "icache_misses": 116 | name = "icache_miss_per_pkt" 117 | 118 | self.logged[name] = float(p['value_per_pkt']) 119 | 120 | extra_msg = self.extra(results) 121 | 122 | self.result_message = "TP=%.2f Mbits/s IDLE=%.2f, PPS=%.2f%s%s" % \ 123 | (speed, idle, total_pps, perf_msg, extra_msg) 124 | 125 | self.logged['test_time'] = self.test_time 126 | 127 | class PerfBarrierPerPktTest(PerfPerPktTest): 128 | '''Count barrier related perf events on a per packet basis''' 129 | def perf_events(self): 130 | return ["cycles", "instructions", "data_sync_barrier", "data_mem_barrier"] 131 | 132 | def extra(self, perf_parse): 133 | return "" 134 | 135 | class PerfLockPerPktTest(PerfPerPktTest): 136 | '''Count lock related perf events on a per packet basis''' 137 | def perf_events(self): 138 | return ["cycles", "instructions", "load_exclusive", "store_exclusive"] 139 | 140 | def extra(self, perf_parse): 141 | return "" 142 | 143 | class PerfUnalignedPerPktTest(PerfPerPktTest): 144 | '''Count unaligned load/store perf events on a per packet basis''' 145 | def perf_events(self): 146 | return ["cycles", "instructions", "unaligned_load", "unaligned_store"] 147 | 148 | def extra(self, perf_parse): 149 | return "" 150 | 151 | class PerfPerPktTestWifi(PerfPerPktTest): 152 | '''Count various perf events on a per packet basis over wifi''' 153 | def runTest(self): 154 | # for wlan since it's not reporting packets properly, we just assign 155 | # it to None and add logic in the parse section to take the other iface 156 | # packet count if this is none 157 | super(PerfPerPktTestWifi, self).runTest(client=wlan, client_name=None) 158 | -------------------------------------------------------------------------------- /tests/ping.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | import lib 10 | from devices import board, wan, lan, wlan, prompt 11 | 12 | class RouterPingWanDev(rootfs_boot.RootFSBootTest): 13 | '''Router can ping device through WAN interface.''' 14 | def runTest(self): 15 | if not wan: 16 | msg = 'No WAN Device defined, skipping ping WAN test.' 17 | lib.common.test_msg(msg) 18 | self.skipTest(msg) 19 | board.sendline('\nping -c5 192.168.0.1') 20 | board.expect('5 packets received', timeout=10) 21 | board.expect(prompt) 22 | def recover(self): 23 | board.sendcontrol('c') 24 | 25 | class RouterPingInternet(rootfs_boot.RootFSBootTest): 26 | '''Router can ping internet address by IP.''' 27 | def runTest(self): 28 | board.sendline('\nping -c2 8.8.8.8') 29 | board.expect('2 packets received', timeout=10) 30 | board.expect(prompt) 31 | 32 | class RouterPingInternetName(rootfs_boot.RootFSBootTest): 33 | '''Router can ping internet address by name.''' 34 | def runTest(self): 35 | board.sendline('\nping -c2 www.google.com') 36 | board.expect('2 packets received', timeout=10) 37 | board.expect(prompt) 38 | 39 | class LanDevPingRouter(rootfs_boot.RootFSBootTest): 40 | '''Device on LAN can ping router.''' 41 | def runTest(self): 42 | if not lan: 43 | msg = 'No LAN Device defined, skipping ping test from LAN.' 44 | lib.common.test_msg(msg) 45 | self.skipTest(msg) 46 | lan.sendline('\nping -i 0.2 -c 5 192.168.1.1') 47 | lan.expect('PING ') 48 | lan.expect('5 received', timeout=15) 49 | lan.expect(prompt) 50 | 51 | class LanDevPingWanDev(rootfs_boot.RootFSBootTest): 52 | '''Device on LAN can ping through router.''' 53 | def runTest(self): 54 | if not lan: 55 | msg = 'No LAN Device defined, skipping ping test from LAN.' 56 | lib.common.test_msg(msg) 57 | self.skipTest(msg) 58 | if not wan: 59 | msg = 'No WAN Device defined, skipping ping WAN test.' 60 | lib.common.test_msg(msg) 61 | self.skipTest(msg) 62 | lan.sendline('\nping -i 0.2 -c 5 192.168.0.1') 63 | lan.expect('PING ') 64 | lan.expect('5 received', timeout=15) 65 | lan.expect(prompt) 66 | def recover(self): 67 | lan.sendcontrol('c') 68 | 69 | class LanDevPingInternet(rootfs_boot.RootFSBootTest): 70 | '''Device on LAN can ping through router to internet.''' 71 | def runTest(self): 72 | if not lan: 73 | msg = 'No LAN Device defined, skipping ping test from LAN.' 74 | lib.common.test_msg(msg) 75 | self.skipTest(msg) 76 | lan.sendline('\nping -c2 8.8.8.8') 77 | lan.expect('2 received', timeout=10) 78 | lan.expect(prompt) 79 | def recover(self): 80 | lan.sendcontrol('c') 81 | -------------------------------------------------------------------------------- /tests/ping6.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | from devices import board, wan, lan, wlan, prompt 10 | 11 | class LanDevPing6Router(rootfs_boot.RootFSBootTest): 12 | '''Device on LAN can ping6 router.''' 13 | def runTest(self): 14 | lan.sendline('\nping6 -i 0.2 -c 20 4aaa::1') 15 | lan.expect('PING ') 16 | lan.expect(' ([0-9]+) received') 17 | n = int(lan.match.group(1)) 18 | lan.expect(prompt) 19 | assert n > 0 20 | 21 | class LanDevPing6WanDev(rootfs_boot.RootFSBootTest): 22 | '''Device on LAN can ping6 through router.''' 23 | def runTest(self): 24 | # Make Lan-device ping Wan-Device 25 | lan.sendline('\nping6 -i 0.2 -c 20 5aaa::6') 26 | lan.expect('PING ') 27 | lan.expect(' ([0-9]+) received') 28 | n = int(lan.match.group(1)) 29 | lan.expect(prompt) 30 | assert n > 0 31 | -------------------------------------------------------------------------------- /tests/qdisc.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | from devices import board, wan, lan, wlan, prompt 10 | 11 | class DelQdisc(rootfs_boot.RootFSBootTest): 12 | '''Tries to remove qdisc root node''' 13 | def runTest(self): 14 | board.sendline('tc qdisc del dev eth0 root') 15 | i = board.expect(['RTNETLINK answers: No such file or directory'] + board.prompt) 16 | if i == 0: 17 | raise Exception("Failed to delete all qdiscs") 18 | -------------------------------------------------------------------------------- /tests/samba.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import random 9 | import re 10 | 11 | import rootfs_boot 12 | from devices import board, wan, lan, wlan, prompt 13 | 14 | class SambaShare(rootfs_boot.RootFSBootTest): 15 | '''Setup and run Samba and test connection.''' 16 | def runTest(self): 17 | board.sendline('rm -f /etc/config/samba; opkg update; opkg install --force-reinstall samba36-server samba36-client kmod-fs-cifs') 18 | board.expect('Configuring samba36-server') 19 | board.expect(prompt) 20 | board.sendline('mkdir -p /tmp/samba; chmod a+rwx /tmp/samba; rm -rf /tmp/samba/*') 21 | board.expect(prompt) 22 | board.sendline('uci set samba.@samba[0].homes=0; uci delete samba.@sambashare[0]; uci add samba sambashare; uci set samba.@sambashare[0]=sambashare; uci set samba.@sambashare[0].name="boardfarm-test"; uci set samba.@sambashare[0].path="/tmp/samba"; uci set samba.@sambashare[0].read_only="no"; uci set samba.@sambashare[0].guest_ok="yes"; uci commit samba') 23 | board.expect(prompt) 24 | board.sendline('/etc/init.d/samba restart') 25 | board.sendline('smbclient -N -L 127.0.0.1') 26 | board.expect('boardfarm-test') 27 | board.expect(prompt) 28 | lan.sendline('smbclient -N -L 192.168.1.1') 29 | lan.expect('boardfarm-test') 30 | lan.expect(prompt) 31 | lan.sendline('mkdir -p /mnt/samba; mount -o guest //192.168.1.1/boardfarm-test /mnt/samba') 32 | lan.expect(prompt) 33 | lan.sendline('echo boardafarm-testing-string > /mnt/samba/test') 34 | lan.expect(prompt) 35 | lan.sendline('umount /mnt/samba') 36 | lan.expect(prompt) 37 | board.sendline('cat /tmp/samba/test') 38 | board.expect('boardafarm-testing-string') 39 | board.expect(prompt) 40 | -------------------------------------------------------------------------------- /tests/ssh_forward_port.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import time 9 | import unittest2 10 | import rootfs_boot 11 | import lib 12 | import pexpect 13 | import sys 14 | from devices import board, wan, lan, wlan, prompt 15 | 16 | class SshWanDetect(rootfs_boot.RootFSBootTest): 17 | '''Can access main web GUI page.''' 18 | @lib.common.run_once 19 | def runTest(self): 20 | super(SshWanDetect, self).runTest() 21 | 22 | board.uci_allow_wan_ssh() 23 | 24 | ipaddr = board.get_interface_ipaddr(board.wan_iface) 25 | port = "22" 26 | 27 | if wan: 28 | t = wan 29 | else: 30 | t = pexpect.spawn("bash") 31 | 32 | sp = lib.common.spawn_ssh_pexpect(ipaddr, "root", 33 | "password", prompt="root@OpenWrt", port=port, via=wan) 34 | sp.sendline("exit") 35 | -------------------------------------------------------------------------------- /tests/status.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import re 9 | import rootfs_boot 10 | from devices import board, wan, lan, wlan, prompt 11 | 12 | class Logread(rootfs_boot.RootFSBootTest): 13 | '''Recorded syslog.''' 14 | def runTest(self): 15 | board.sendline('\nlogread') 16 | board.expect('logread') 17 | board.expect('OpenWrt', timeout=5) 18 | board.expect(prompt) 19 | 20 | class DiskUse(rootfs_boot.RootFSBootTest): 21 | '''Checked disk use.''' 22 | def runTest(self): 23 | board.sendline('\ndf -k') 24 | board.expect('Filesystem', timeout=5) 25 | board.expect(prompt) 26 | board.sendline('du -k | grep -v ^0 | sort -n | tail -20') 27 | board.expect(prompt) 28 | 29 | class TopCheck(rootfs_boot.RootFSBootTest): 30 | '''Ran "top" to see current processes.''' 31 | def runTest(self): 32 | board.sendline('\ntop -b -n 1') 33 | board.expect('Mem:', timeout=5) 34 | try: 35 | board.expect(prompt, timeout=2) 36 | except: 37 | # some versions of top do not support '-n' 38 | # must CTRL-C to kill top 39 | board.sendcontrol('c') 40 | 41 | class UciShow(rootfs_boot.RootFSBootTest): 42 | '''Dumped all current uci settings.''' 43 | def runTest(self): 44 | board.sendline('\nls -l /etc/config/') 45 | board.expect('/etc/config/', timeout=5) 46 | board.expect(prompt) 47 | board.sendline('ls -l /etc/config/ | wc -l') 48 | board.expect('(\d+)\r\n') 49 | num_files = int(board.match.group(1)) 50 | board.expect(prompt) 51 | board.sendline('uci show') 52 | board.expect(prompt, searchwindowsize=50) 53 | self.result_message = 'Dumped all current uci settings from %s files in /etc/config/.' % num_files 54 | 55 | class DhcpLeaseCheck(rootfs_boot.RootFSBootTest): 56 | '''Checked dhcp.leases file.''' 57 | def runTest(self): 58 | board.sendline('\ncat /tmp/dhcp.leases') 59 | board.expect('leases') 60 | board.expect(prompt) 61 | 62 | class IfconfigCheck(rootfs_boot.RootFSBootTest): 63 | '''Ran 'ifconfig' to check interfaces.''' 64 | def runTest(self): 65 | board.sendline('\nifconfig') 66 | board.expect('ifconfig') 67 | board.expect(prompt) 68 | results = re.findall('([A-Za-z0-9-\.]+)\s+Link.*\n.*addr:([^ ]+)', board.before) 69 | tmp = ', '.join(["%s %s" % (x, y) for x, y in results]) 70 | board.sendline('route -n') 71 | board.expect(prompt) 72 | self.result_message = 'ifconfig shows ip addresses: %s' % tmp 73 | 74 | class MemoryUse(rootfs_boot.RootFSBootTest): 75 | '''Checked memory use.''' 76 | def runTest(self): 77 | board.sendline('\nsync; echo 3 > /proc/sys/vm/drop_caches') 78 | board.expect('echo 3') 79 | board.expect(prompt, timeout=5) 80 | # There appears to be a tiny, tiny chance that 81 | # /proc/meminfo won't exist, so try one more time. 82 | for i in range(2): 83 | try: 84 | board.sendline('cat /proc/meminfo') 85 | board.expect('MemTotal:\s+(\d+) kB', timeout=5) 86 | break 87 | except: 88 | pass 89 | mem_total = int(board.match.group(1)) 90 | board.expect('MemFree:\s+(\d+) kB') 91 | mem_free = int(board.match.group(1)) 92 | board.expect(prompt) 93 | mem_used = mem_total - mem_free 94 | self.result_message = 'Used memory: %s MB. Free memory: %s MB.' % (mem_used/1000, mem_free/1000) 95 | self.logged['mem_used'] = mem_used/1000 96 | 97 | class SleepHalfMinute(rootfs_boot.RootFSBootTest): 98 | '''Slept 30 seconds.''' 99 | def recover(self): 100 | board.sendcontrol('c') 101 | def runTest(self): 102 | board.check_output('date') 103 | board.check_output('sleep 30', timeout=40) 104 | board.check_output('date') 105 | 106 | class Sleep1Minute(rootfs_boot.RootFSBootTest): 107 | '''Slept 1 minute.''' 108 | def recover(self): 109 | board.sendcontrol('c') 110 | def runTest(self): 111 | board.check_output('date') 112 | board.check_output('sleep 60', timeout=70) 113 | board.check_output('date') 114 | 115 | class Sleep2Minutes(rootfs_boot.RootFSBootTest): 116 | '''Slept 2 minutes.''' 117 | def recover(self): 118 | board.sendcontrol('c') 119 | def runTest(self): 120 | # Connections time out after 2 minutes, so this is useful to have. 121 | board.sendline('\n date') 122 | board.expect('date') 123 | board.expect(prompt) 124 | board.sendline('sleep 120') 125 | board.expect('sleep ') 126 | board.expect(prompt, timeout=130) 127 | board.sendline('date') 128 | board.expect('date') 129 | board.expect(prompt) 130 | 131 | class Sleep5Minutes(rootfs_boot.RootFSBootTest): 132 | '''Slept 5 minutes.''' 133 | def recover(self): 134 | board.sendcontrol('c') 135 | def runTest(self): 136 | board.sendline('\n date') 137 | board.expect('date') 138 | board.expect(prompt) 139 | board.sendline('sleep 300') 140 | board.expect('sleep ') 141 | board.expect(prompt, timeout=310) 142 | board.sendline('date') 143 | board.expect('date') 144 | board.expect(prompt) 145 | -------------------------------------------------------------------------------- /tests/sysupgrade.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import time 9 | import unittest2 10 | import rootfs_boot 11 | import lib 12 | from devices import board, wan, lan, wlan, prompt 13 | 14 | class Sysupgrade(rootfs_boot.RootFSBootTest): 15 | '''Upgrading via sysupgrade works.''' 16 | def runTest(self): 17 | super(Sysupgrade, self).runTest() 18 | 19 | if not hasattr(self.config, "SYSUPGRADE_NEW"): 20 | self.skipTest("no sysupgrade specified") 21 | 22 | # output some stuff before we kill all the logs in the system, just 23 | # to be able to review these logs later 24 | board.sendline('logread') 25 | board.expect(prompt, timeout=120) 26 | board.sendline('dmesg') 27 | board.expect(prompt) 28 | 29 | # This test can damage flash, so to properly recover we need 30 | # to reflash upon recovery 31 | self.reflash = True 32 | 33 | board.sendline('touch /etc/config/TEST') 34 | board.expect('/etc/config/TEST') 35 | board.expect(prompt) 36 | 37 | board.sendline("cd /tmp") 38 | filename = board.prepare_file(self.config.SYSUPGRADE_NEW) 39 | new_filename = board.tftp_get_file(board.tftp_server, 40 | filename, 240) 41 | board.sendline("sysupgrade -v /tmp/%s" % 42 | new_filename) 43 | board.expect("Restarting system", timeout=180) 44 | 45 | lib.common.wait_for_boot(board) 46 | board.boot_linux() 47 | board.wait_for_linux() 48 | 49 | board.sendline('ls -alh /etc/config/TEST') 50 | board.expect('/etc/config/TEST\r\n') 51 | board.expect(prompt) 52 | -------------------------------------------------------------------------------- /tests/ubus.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import re 9 | import rootfs_boot 10 | import json 11 | import time 12 | from devices import board, wan, lan, wlan, prompt 13 | 14 | def ubus_call_raw(payload, ipaddr="192.168.1.1"): 15 | curl_cmd = "curl -d '%s' http://%s/ubus" % (json.dumps(payload), ipaddr) 16 | lan.sendline(curl_cmd) 17 | lan.expect("\r\n") 18 | lan.expect(prompt) 19 | 20 | return json.loads(lan.before) 21 | 22 | def ubus_call(session_id, ubus_object, ubus_func, params): 23 | j = { "jsonrpc": "2.0", 24 | "id": 1, 25 | "method": "call", 26 | "params": [ session_id, 27 | ubus_object, 28 | ubus_func, 29 | params 30 | ] 31 | } 32 | return ubus_call_raw(j) 33 | 34 | def ubus_check_error(reply, assert_on_err=True): 35 | if "error" in reply: 36 | print("Got error in reply") 37 | print(reply) 38 | 39 | assert(not assert_on_err) 40 | 41 | def ubus_login_raw(username="root", password="password"): 42 | json = { "jsonrpc": "2.0", 43 | "id": 1, 44 | "method": "call", 45 | "params": [ "00000000000000000000000000000000", 46 | "session", 47 | "login", { "username": username, 48 | "password": password } 49 | ] 50 | } 51 | return ubus_call_raw(json) 52 | 53 | def ubus_login_session(username="root", password="password"): 54 | reply = ubus_login_raw(username, password) 55 | 56 | ubus_check_error(reply) 57 | 58 | return reply['result'][1]['ubus_rpc_session'] 59 | 60 | def ubus_network_restart(session_id): 61 | json = { "jsonrpc": "2.0", 62 | "id": 1, 63 | "method": "call", 64 | "params": [ session_id, 65 | "network", 66 | "restart", 67 | { } 68 | ] 69 | } 70 | 71 | reply = ubus_call_raw(json) 72 | ubus_check_error(reply) 73 | 74 | def ubus_system_reboot(session_id): 75 | json = { "jsonrpc": "2.0", 76 | "id": 1, 77 | "method": "call", 78 | "params": [ session_id, 79 | "foo", 80 | "bar", 81 | { } 82 | ] 83 | } 84 | 85 | reply = ubus_call_raw(json) 86 | ubus_check_error(reply) 87 | 88 | class UBusTestNetworkRestart(rootfs_boot.RootFSBootTest): 89 | '''Various UBus tests''' 90 | def runTest(self): 91 | 92 | for i in range(1000): 93 | print("\nRunning iteration of ubus json-rpc network restart nubmer %s\n" % i) 94 | session_id = ubus_login_session() 95 | print("\nLogged in with sessionid = %s\n"% session_id) 96 | ubus_network_restart(session_id) 97 | # wait some amount of time, we can get a new session id before restart 98 | # really starts 99 | time.sleep(5) 100 | 101 | class UBusTestSystemReboot(rootfs_boot.RootFSBootTest): 102 | '''Various UBus tests''' 103 | def runTest(self): 104 | 105 | for i in range(1000): 106 | print("\nRunning iteration of ubus json-rpc system reboot nubmer %s\n" % i) 107 | session_id = ubus_login_session() 108 | print("\nLogged in with sessionid = %s\n" % session_id) 109 | 110 | ubus_system_reboot(session_id) 111 | board.wait_for_linux() 112 | 113 | class UBusTestKrouter(rootfs_boot.RootFSBootTest): 114 | '''Krouter UBus tests''' 115 | def runTest(self): 116 | ubus_call("00000000000000000000000000000000", "krouter", "add_krouter_endpoint", { "macaddr": "00:11:22:33:44:55" }) 117 | session_id = ubus_login_session() 118 | ret = ubus_call(session_id, "krouter", "is_krouter_endpoint", { "macaddr": "00:11:22:33:44:55" }) 119 | print ret 120 | -------------------------------------------------------------------------------- /tests/uci_wireless.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import re 9 | 10 | import rootfs_boot 11 | from devices import board, wan, lan, wlan, prompt 12 | from lib.wifi import * 13 | 14 | class UciShowWireless(rootfs_boot.RootFSBootTest): 15 | '''UCI lists wifi interfaces.''' 16 | def runTest(self): 17 | wlan_iface = wifi_interface(board) 18 | if wlan_iface is None: 19 | self.skipTest("No wifi interfaces detected, skipping..") 20 | 21 | board.sendline('\nuci show wireless') 22 | board.expect('uci show wireless') 23 | board.expect(prompt) 24 | wifi_interfaces = re.findall('wireless.*=wifi-device', board.before) 25 | self.result_message = "UCI shows %s wifi interface(s)." % (len(wifi_interfaces)) 26 | self.logged['num_ifaces'] = len(wifi_interfaces) 27 | # Require that at least one wifi interface is present 28 | assert len(wifi_interfaces) > 0 29 | -------------------------------------------------------------------------------- /tests/uname.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | from devices import board, wan, lan, wlan, prompt 10 | 11 | class Uname(rootfs_boot.RootFSBootTest): 12 | '''Checked board system information.''' 13 | def runTest(self): 14 | board.sendline('\nuname -a') 15 | board.expect('uname -a', timeout=6) 16 | board.expect(prompt) 17 | info = board.before.replace('\r', '').replace('\n', '') 18 | self.result_message = info 19 | board.sendline('uname -m') 20 | board.expect('uname -m') 21 | board.expect(prompt) 22 | self.logged['machine'] = board.before.replace('\r', '').replace('\n', '') 23 | board.sendline('uname -r') 24 | board.expect('uname -r') 25 | board.expect(prompt) 26 | self.logged['release'] = board.before.replace('\r', '').replace('\n', '') 27 | board.sendline('uname -s') 28 | board.expect('uname -s') 29 | board.expect(prompt) 30 | self.logged['kernel'] = board.before.replace('\r', '').replace('\n', '') 31 | -------------------------------------------------------------------------------- /tests/vmstat.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import re 9 | import rootfs_boot 10 | from devices import board, wan, lan, wlan, prompt 11 | 12 | class ProcVmstat(rootfs_boot.RootFSBootTest): 13 | '''Check /proc/vmstat stats.''' 14 | def runTest(self): 15 | # Log every field value found 16 | board.sendline('\ncat /proc/vmstat') 17 | board.expect('cat /proc/vmstat') 18 | board.expect(prompt) 19 | results = re.findall('(\w+) (\d+)', board.before) 20 | for key, value in results: 21 | self.logged[key] = int(value) 22 | # Display extra info 23 | board.sendline('cat /proc/slabinfo /proc/buddyinfo /proc/meminfo') 24 | board.expect('cat /proc/') 25 | board.expect(prompt) 26 | board.sendline('cat /proc/vmallocinfo') 27 | board.expect('cat /proc/vmallocinfo') 28 | board.expect(prompt) 29 | -------------------------------------------------------------------------------- /tests/webbrowse.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import random 9 | import rootfs_boot 10 | from devices import board, wan, lan, wlan, prompt 11 | 12 | class RandomWebBrowse(rootfs_boot.RootFSBootTest): 13 | '''Created light web traffic.''' 14 | def runTest(self): 15 | urls = ['www.amazon.com', 16 | 'www.apple.com', 17 | 'www.baidu.com', 18 | 'www.bing.com', 19 | 'www.cnn.com', 20 | 'www.ebay.com', 21 | 'www.facebook.com', 22 | 'www.google.com', 23 | 'www.imdb.com', 24 | 'www.imgur.com', 25 | 'www.instagram.com', 26 | 'www.linkedin.com', 27 | 'www.microsoft.com', 28 | 'www.nbcnews.com', 29 | 'www.netflix.com', 30 | 'www.pinterest.com', 31 | 'www.reddit.com', 32 | 'www.twitter.com', 33 | 'www.wikipedia.org', 34 | 'www.yahoo.com', 35 | ] 36 | #urls = 2 * urls # browse more 37 | random.shuffle(urls) 38 | user = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:39.0) Gecko/20100101 Firefox/39.0' 39 | cmd = "wget -Hp http://%(url)s " + \ 40 | "-e robots=off " + \ 41 | "-P /tmp/webbrowse-test " + \ 42 | "-T 10 " + \ 43 | "--header='Accept: text/html' " + \ 44 | "-U '%(user)s' " + \ 45 | "2>&1 | tail -1" 46 | for url in urls: 47 | lan.sendline("mkdir -p /tmp/webbrowse-test") 48 | print("\n%s" % url) 49 | tmp = cmd % {'url': url, 'user': user} 50 | lan.sendline(tmp) 51 | try: 52 | lan.expect('Downloaded:', timeout=20) 53 | except Exception: 54 | lan.sendcontrol('c') 55 | lan.expect(prompt) 56 | lan.sendline("rm -rf /tmp/webbrowse-test") 57 | lan.expect(prompt) 58 | -------------------------------------------------------------------------------- /tests/webgui.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | from devices import board, wan, lan, wlan, prompt 10 | 11 | class Webserver_Running(rootfs_boot.RootFSBootTest): 12 | '''Router webserver is running.''' 13 | def runTest(self): 14 | board.sendline('\nps | grep -v grep | grep http') 15 | board.expect('uhttpd') 16 | board.expect(prompt) 17 | 18 | class WebGUI_Access(rootfs_boot.RootFSBootTest): 19 | '''Router webpage available to LAN-device at http://192.168.1.1/.''' 20 | def runTest(self): 21 | url = 'http://192.168.1.1/' 22 | lan.sendline('\ncurl -v %s' % url) 23 | lan.expect('') 26 | lan.expect('') 27 | lan.expect(prompt) 28 | 29 | class WebGUI_NoStackTrace(rootfs_boot.RootFSBootTest): 30 | '''Router webpage at cgi-bin/luci contains no stack traceback.''' 31 | def runTest(self): 32 | board.sendline('\ncurl -s http://127.0.0.1/cgi-bin/luci | head -15') 33 | board.expect('cgi-bin/luci') 34 | board.expect(prompt) 35 | assert 'traceback' not in board.before 36 | 37 | class Webserver_Download(rootfs_boot.RootFSBootTest): 38 | '''Downloaded small file from router webserver in reasonable time.''' 39 | def runTest(self): 40 | board.sendline('\nhead -c 1000000 /dev/urandom > /www/deleteme.txt') 41 | board.expect('head ', timeout=5) 42 | board.expect(prompt) 43 | lan.sendline('\ncurl -m 25 http://192.168.1.1/deleteme.txt > /dev/null') 44 | lan.expect('Total', timeout=5) 45 | lan.expect('100 ', timeout=10) 46 | lan.expect(prompt, timeout=10) 47 | board.sendline('\nrm -f /www/deleteme.txt') 48 | board.expect('deleteme.txt') 49 | board.expect(prompt) 50 | def recover(self): 51 | board.sendcontrol('c') 52 | lan.sendcontrol('c') 53 | board.sendline('rm -f /www/deleteme.txt') 54 | board.expect(prompt) 55 | -------------------------------------------------------------------------------- /tests/webui_tests.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 8 | 9 | import rootfs_boot 10 | import lib 11 | from devices import board, wan, lan, wlan, prompt 12 | from selenium.webdriver import ActionChains 13 | 14 | class WebTest(rootfs_boot.RootFSBootTest): 15 | '''Login to LuCI''' 16 | def setUp(self): 17 | super(WebTest, self).setUp() 18 | if not lan: 19 | msg = 'No LAN Device defined, skipping web test.' 20 | lib.common.test_msg(msg) 21 | self.skipTest(msg) 22 | 23 | # Set password, just to be sure 24 | board.sendline("passwd") 25 | board.expect("New password:", timeout=8) 26 | board.sendline("password") 27 | board.expect("Retype password:") 28 | board.sendline("password") 29 | board.expect(prompt) 30 | 31 | # Create a driver 32 | self.driver = lib.common.phantom_webproxy_driver('http://' + lan.name + ':8080') 33 | self.driver.get("http://192.168.1.1/cgi-bin/luci") 34 | self.assertIn('192.168.1.1', self.driver.current_url) 35 | self.assertIn('LuCI', self.driver.title) 36 | self.driver.find_element_by_name('luci_password').send_keys('password') 37 | self.driver.find_element_by_class_name('cbi-button-apply').submit() 38 | self.driver.find_element_by_xpath("//ul/li/a[contains(text(),'Status')]") 39 | 40 | class WebOverview(WebTest): 41 | '''Check overview page''' 42 | def runTest(self): 43 | print('Checking overview page') 44 | action_chains = ActionChains(self.driver) 45 | status_menu = self.driver.find_element_by_xpath("//ul/li/a[contains(text(),'Status')]") 46 | overview_menu = self.driver.find_element_by_xpath("//ul/li/a[contains(text(),'Overview')]") 47 | action_chains.move_to_element(status_menu).click(overview_menu).perform() 48 | self.assertIn('Overview', self.driver.title) 49 | print('Managed to switch to overview page') 50 | for i in [ 'System', 'Memory', 'Network', 'DHCP Leases' ]: 51 | self.driver.find_element_by_xpath("//fieldset/legend[contains(text(),'" + i + "')]") 52 | print(' * overview page contains section ' + i) 53 | -------------------------------------------------------------------------------- /tests/wifi_cycle.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import string 9 | import time 10 | 11 | import rootfs_boot 12 | from devices import board, wan, lan, wlan, prompt 13 | from lib.wifi import * 14 | 15 | def wifi_cycle(board, num_times=5, wlan_iface="ath0"): 16 | '''Enable and disable wifi some number of times.''' 17 | if wifi_on(board): 18 | disable_wifi(board, wlan_iface) 19 | wifi_name = randomSSIDName() 20 | board.sendline('uci set wireless.@wifi-iface[0].ssid=%s' % wifi_name) 21 | board.expect(prompt) 22 | board.sendline('uci set wireless.@wifi-iface[0].encryption=psk2') 23 | board.expect(prompt) 24 | board.sendline('uci set wireless.@wifi-iface[0].key=%s' % randomSSIDName()) 25 | board.expect(prompt) 26 | board.sendline('echo "7 7 7 7" > /proc/sys/kernel/printk') 27 | board.expect(prompt) 28 | for i in range(1, num_times+1): 29 | enable_wifi(board) 30 | wait_wifi_up(board, wlan_iface=wlan_iface) 31 | disable_wifi(board, wlan_iface=wlan_iface) 32 | print("\n\nEnabled and disabled WiFi %s times." % i) 33 | board.sendline('echo "1 1 1 7" > /proc/sys/kernel/printk') 34 | board.expect(prompt) 35 | 36 | class WiFiOnOffCycle(rootfs_boot.RootFSBootTest): 37 | '''Enabled and disabled wifi once.''' 38 | def runTest(self): 39 | wlan_iface = wifi_interface(board) 40 | if wlan_iface is None: 41 | self.skipTest("No wifi interfaces detected, skipping..") 42 | 43 | wifi_cycle(board, num_times=1, wlan_iface=wlan_iface) 44 | 45 | class WiFiOnOffCycle5(rootfs_boot.RootFSBootTest): 46 | '''Enabled and disabled wifi 5 times.''' 47 | def runTest(self): 48 | wlan_iface = wifi_interface(board) 49 | if wlan_iface is None: 50 | self.skipTest("No wifi interfaces detected, skipping..") 51 | 52 | wifi_cycle(board, num_times=5, wlan_iface=wlan_iface) 53 | 54 | class WiFiOnOffCycle20(rootfs_boot.RootFSBootTest): 55 | '''Enabled and disabled wifi 20 times.''' 56 | def runTest(self): 57 | wlan_iface = wifi_interface(board) 58 | if wlan_iface is None: 59 | self.skipTest("No wifi interfaces detected, skipping..") 60 | 61 | wifi_cycle(board, num_times=20, wlan_iface=wlan_iface) 62 | # Leave with wifi enabled 63 | enable_wifi(board) 64 | wait_wifi_up(board, wlan_iface=wlan_iface) 65 | -------------------------------------------------------------------------------- /tests/wifi_memuse.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import lib 9 | import rootfs_boot 10 | import time 11 | import re 12 | 13 | import lib.wifi 14 | from devices import board, wan, lan, wlan, prompt 15 | 16 | class WiFiMemUse(rootfs_boot.RootFSBootTest): 17 | '''Measured WiFi memory use when enabled.''' 18 | def recover(self): 19 | board.sendcontrol('c') 20 | def runTest(self): 21 | # Disable WiFi 22 | board.sendline('\nwifi detect > /etc/config/wireless') 23 | board.expect('wifi detect') 24 | board.expect(prompt) 25 | board.sendline('uci commit wireless; wifi') 26 | board.expect(prompt) 27 | # One of these commands should be available 28 | board.sendline('iwconfig || iwinfo') 29 | board.expect(prompt) 30 | memfree_wifi_off = board.get_memfree() 31 | # Enable WiFi 32 | lib.wifi.enable_all_wifi_interfaces(board) 33 | time.sleep(90) # give time to start and settle 34 | board.sendline('iwconfig || iwinfo') 35 | board.expect(['ESSID', 'IEEE']) 36 | board.expect(prompt) 37 | memfree_wifi_on = board.get_memfree() 38 | mem_used = (int(memfree_wifi_off)-int(memfree_wifi_on)) / 1000 39 | self.result_message = 'Enabling all WiFi interfaces uses %s MB.' % (mem_used) 40 | self.logged['mem_used'] = mem_used 41 | 42 | class TurnOnWifi(rootfs_boot.RootFSBootTest): 43 | '''Turn on all WiFi interfaces.''' 44 | def runTest(self): 45 | wlan_iface = lib.wifi.wifi_interface(board) 46 | lib.wifi.enable_wifi(board) 47 | lib.wifi.wait_wifi_up(board, wlan_iface=wlan_iface) 48 | board.sendline('\nifconfig') 49 | board.expect('HWaddr') 50 | board.expect(prompt) 51 | -------------------------------------------------------------------------------- /tests/wifi_vap_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import wlan_set_ssid 9 | from lib.wifi import * 10 | from devices import board, wan, lan, wlan, prompt 11 | import time 12 | 13 | class WlanVAP(wlan_set_ssid.WlanSetSSID): 14 | '''Test multiple VAPs up and down''' 15 | def runTest(self): 16 | enable_wifi(board, index=0) 17 | # TODO: make sure we have a radio 18 | enable_wifi(board, index=1) 19 | 20 | for i in range(1, 16): 21 | wifi_add_vap(board, "wifi0", randomSSIDName()) 22 | 23 | for i in range(1, 16): 24 | wifi_add_vap(board, "wifi1", randomSSIDName()) 25 | 26 | for i in range(0, 20): 27 | board.sendline('wifi down') 28 | board.expect('wifi down') 29 | board.expect(prompt, timeout=480) 30 | board.sendline('wifi up') 31 | board.expect('wifi up') 32 | board.expect(prompt, timeout=480) 33 | 34 | # expect 32 vaps to be present 35 | for i in range(0, 5): 36 | try: 37 | time.sleep(10) 38 | board.sendline('ifconfig -a | grep ath | wc -l') 39 | board.expect('32') 40 | board.expect(prompt) 41 | except: 42 | if i == 4: 43 | assert False 44 | else: 45 | break 46 | 47 | for i in range(1, 16): 48 | wifi_del_vap(board, -1) 49 | 50 | for i in range(1, 16): 51 | wifi_del_vap(board, -1) 52 | -------------------------------------------------------------------------------- /tests/wlan_associate.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import unittest2 9 | import rootfs_boot 10 | import lib 11 | import sys 12 | import pexpect 13 | import time 14 | import wlan_set_ssid 15 | import re 16 | from devices import board, wan, lan, wlan, prompt 17 | from lib.wifi import * 18 | 19 | class WlanAssociate(wlan_set_ssid.WlanSetSSID): 20 | '''Wifi device connected and had internet access.''' 21 | def wlan_setup(self): 22 | wlan.sendline('\napt-get install -qy usbutils wireless-tools') 23 | wlan.expect('Reading package') 24 | wlan.expect(prompt) 25 | wlan.sendline('killall wpa_supplicant') 26 | wlan.expect(prompt) 27 | 28 | def recover(self): 29 | wlan.sendcontrol('c') 30 | wlan.sendcontrol('c') 31 | 32 | @lib.common.run_once 33 | def runTest(self): 34 | super(WlanAssociate, self).runTest() 35 | wlan_iface = wifi_interface(board) 36 | if wlan_iface is None: 37 | self.skipTest("No wifi interfaces detected, skipping..") 38 | if wlan is None: 39 | self.skipTest("No wlan VM, skipping test..") 40 | 41 | #Determine if we are using a beeliner x86 host. If not, default to usb drivers. 42 | #Would like to push this out to a library. 43 | wlan.sendline('lspci |grep -q Atheros; echo $?') 44 | wlan.expect('(\d+)\r\n') 45 | check_atheros = int(wlan.match.group(1)) 46 | 47 | if check_atheros is not 0: 48 | print("Creating realtek interface.") 49 | wlan.sendline('\napt-get install -qy firmware-realtek') 50 | wlan.expect('Reading package') 51 | wlan.expect(prompt) 52 | wlan.sendline('rmmod rtl8192cu 8812au') 53 | wlan.expect(prompt) 54 | wlan.sendline('modprobe rtl8192cu') 55 | wlan.expect(prompt) 56 | wlan.sendline('modprobe 8812au') 57 | wlan.expect(prompt) 58 | else: 59 | #Check if modules are alerady loaded. If not, load them. 60 | print("Found Atheros hardware, creating interface.") 61 | wlan.sendline('lsmod |grep -q ath_hal; echo $?') 62 | wlan.expect('(\d+)\r\n') 63 | check_loaded = int(wlan.match.group(1)) 64 | 65 | if check_loaded is not 0: 66 | #rc.wlan takes care of insmod, wlanconfig creates wlan0. Both should be in the path. 67 | wlan.sendline('rc.wlan up') 68 | wlan.expect(prompt) 69 | wlan.sendline('wlanconfig wlan0 create wlandev wifi0 wlanmode sta') 70 | wlan.expect(prompt) 71 | 72 | wlan.sendline('rfkill unblock all') 73 | wlan.expect(prompt) 74 | 75 | wlan.sendline('ifconfig wlan0') 76 | wlan.expect('HWaddr') 77 | wlan.expect(prompt) 78 | 79 | wlan.sendline('ifconfig wlan0 down') 80 | wlan.expect(prompt) 81 | 82 | wlan.sendline('ifconfig wlan0 up') 83 | wlan.expect(prompt) 84 | 85 | # wait until the wifi can see the SSID before even trying to join 86 | # not sure how long we should really give this, or who's fault it is 87 | for i in range(0, 20): 88 | try: 89 | wlan.sendline('iwlist wlan0 scan | grep ESSID:') 90 | wlan.expect(self.config.ssid) 91 | wlan.expect(prompt) 92 | except: 93 | lib.common.test_msg("can't see ssid %s, scanning again (%s tries)" % (self.config.ssid, i)) 94 | else: 95 | break 96 | 97 | time.sleep(10) 98 | 99 | for i in range(0, 2): 100 | try: 101 | wlan.sendline('iwconfig wlan0 essid %s' % self.config.ssid) 102 | wlan.expect(prompt) 103 | 104 | # give it some time to associate 105 | time.sleep(20) 106 | 107 | # make sure we assocaited 108 | wlan.sendline('iwconfig wlan0') 109 | wlan.expect('Access Point: ([0-9A-F]{2}[:-]){5}([0-9A-F]{2})') 110 | wlan.expect(prompt) 111 | except: 112 | lib.common.test_msg("Can't associate with ssid %s, trying again (%s tries) " % (self.config.ssid, i)) 113 | else: 114 | break 115 | 116 | # get ip on wlan 117 | wlan.sendline('killall dhclient') 118 | wlan.expect(prompt) 119 | time.sleep(10) 120 | wlan.sendline('dhclient wlan0') 121 | wlan.expect(prompt) 122 | 123 | # for reference 124 | wlan.sendline('ifconfig wlan0') 125 | wlan.expect(prompt) 126 | 127 | # make sure dhcp worked, and for reference of IP 128 | wlan_ip = wlan.get_interface_ipaddr("wlan0") 129 | 130 | # add route to wan 131 | wlan.sendline('ip route add 192.168.0.0/24 via 192.168.1.1') 132 | wlan.expect(prompt) 133 | wlan.sendline('ip route show') 134 | wlan.expect(prompt) 135 | 136 | wlan.sendline('ping 192.168.1.1 -c3') 137 | wlan.expect('3 packets transmitted') 138 | wlan.expect(prompt) 139 | wlan.sendline('curl 192.168.1.1 --connect-timeout 5 > /dev/null 2>&1; echo $?') 140 | wlan.expect('(\d+)\r\n') 141 | curl_success = int(wlan.match.group(1)) 142 | 143 | msg = "Attempt to curl router returns %s\n" % (curl_success) 144 | lib.common.test_msg(msg) 145 | assert (curl_success == 0) 146 | -------------------------------------------------------------------------------- /tests/wlan_set_ssid.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | import lib 10 | import sys 11 | import time 12 | import traceback 13 | from devices import board, wan, lan, wlan, prompt 14 | from lib.wifi import * 15 | 16 | class WlanSetSSID(rootfs_boot.RootFSBootTest): 17 | '''Wifi device came up and was able to set SSID.''' 18 | def wlan_setup(self): 19 | wlan.sendline('\napt-get install -qy firmware-realtek usbutils wireless-tools') 20 | wlan.expect('Reading package') 21 | wlan.expect(prompt) 22 | 23 | @lib.common.run_once 24 | def runTest(self): 25 | wlan_iface = wifi_interface(board) 26 | if wlan_iface is None: 27 | self.skipTest("No wifi interfaces detected, skipping..") 28 | 29 | self.config.ssid = randomSSIDName() 30 | 31 | disable_wifi(board) 32 | uciSetWifiSSID(board, self.config.ssid) 33 | enable_wifi(board) 34 | 35 | # verfiy we have an interface here 36 | if wlan_iface == "ath0": 37 | board.sendline('iwconfig %s' % wlan_iface) 38 | board.expect('%s.*IEEE 802.11.*ESSID.*%s' % (wlan_iface, self.config.ssid)) 39 | else: 40 | board.sendline('iwinfo %s info' % wlan_iface) 41 | board.expect('%s.*ESSID.*%s' % (wlan_iface, self.config.ssid)) 42 | board.expect(prompt) 43 | 44 | # wait for AP to set rate, which means it's done coming up 45 | for i in range(20): 46 | try: 47 | essid, channel, rate, freq = wifi_get_info(board, wlan_iface) 48 | info = "Rate = %s Mb/s, Freq = %s Ghz" % (rate, freq) 49 | time.sleep(5) 50 | if wlan_iface == "ath0": 51 | assert float(rate) > 0 52 | elif wlan_iface == "wlan0": 53 | assert channel > 0 54 | lib.common.test_msg("%s\n" % info) 55 | self.result_message = self.__doc__ + " (%s)" % info 56 | except Exception as e: 57 | traceback.print_exc(file=sys.stdout) 58 | if i < 10: 59 | pass 60 | else: 61 | break 62 | -------------------------------------------------------------------------------- /tests/wlan_set_ssid_wpa2psk.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | import rootfs_boot 9 | import lib 10 | import sys 11 | import time 12 | import traceback 13 | from devices import board, wan, lan, wlan, prompt 14 | from lib.wifi import * 15 | 16 | class WlanSetSSID_WPA2PSK(rootfs_boot.RootFSBootTest): 17 | '''Wifi device came up and was able to set SSID.''' 18 | def wlan_setup(self): 19 | wlan.sendline('\napt-get install -qy firmware-realtek usbutils wireless-tools wpasupplicant') 20 | wlan.expect('Reading package') 21 | wlan.expect(prompt) 22 | 23 | @lib.common.run_once 24 | def runTest(self): 25 | wlan_iface = wifi_interface(board) 26 | wlan_security = "wpa2-psk" 27 | vap_iface = "0" 28 | if wlan_iface is None: 29 | self.skipTest("No wifi interfaces detected, skipping..") 30 | 31 | self.config.ssid = randomSSIDName() 32 | 33 | disable_wifi(board) 34 | uciSetWifiSecurity(board, vap_iface, wlan_security) 35 | uciSetChannel(board, "0", "153") 36 | uciSetWifiSSID(board, self.config.ssid) 37 | enable_wifi(board) 38 | 39 | # verfiy we have an interface here 40 | if wlan_iface == "ath0": 41 | board.sendline('iwconfig %s' % wlan_iface) 42 | board.expect('%s.*IEEE 802.11.*ESSID.*%s' % (wlan_iface, self.config.ssid)) 43 | else: 44 | board.sendline('iwinfo %s info' % wlan_iface) 45 | board.expect('%s.*ESSID.*%s' % (wlan_iface, self.config.ssid)) 46 | board.expect(prompt) 47 | 48 | # wait for AP to set rate, which means it's done coming up 49 | for i in range(20): 50 | try: 51 | essid, channel, rate, freq = wifi_get_info(board, wlan_iface) 52 | info = "Rate = %s Mb/s, Freq = %s Ghz" % (rate, freq) 53 | time.sleep(5) 54 | if wlan_iface == "ath0": 55 | assert float(rate) > 0 56 | elif wlan_iface == "wlan0": 57 | assert channel > 0 58 | lib.common.test_msg("%s\n" % info) 59 | self.result_message = self.__doc__ + " (%s)" % info 60 | except Exception as e: 61 | traceback.print_exc(file=sys.stdout) 62 | if i < 10: 63 | pass 64 | else: 65 | break 66 | -------------------------------------------------------------------------------- /testsuites.cfg: -------------------------------------------------------------------------------- 1 | # Test Suites can also be defined in testsuites.py. 2 | # Use this file for simple test suites. 3 | # Suite names are in brackets [] 4 | # Test names must match tests found in files in the tests directory. 5 | 6 | [connect] 7 | Interact 8 | 9 | [basic] 10 | RootFSBootTest 11 | OpenwrtVersion 12 | OpkgList 13 | KernelModules 14 | MemoryUse 15 | InterfacesShow 16 | LanDevPingRouter 17 | RouterPingWanDev 18 | LanDevPingWanDev 19 | Webserver_Download 20 | UciShowWireless 21 | WiFiOnOffCycle5 22 | RestartNetwork 23 | iPerfBiDirTest 24 | Set_IPv6_Addresses 25 | LanDevPing6Router 26 | LanDevPing6WanDev 27 | iPerfBiDirTestIPV6 28 | 29 | [daily] 30 | @basic 31 | Uname 32 | UciShow 33 | WiFiMemUse 34 | ConnectionRate 35 | PerfPerPktTest 36 | ProcVmstat 37 | 38 | [flash] 39 | RootFSBootTest 40 | 41 | [interact] 42 | RootFSBootTest 43 | Interact 44 | 45 | [short] 46 | RootFSBootTest 47 | SleepHalfMinute 48 | OpenwrtVersion 49 | OpkgList 50 | KernelModules 51 | MemoryUse 52 | InterfacesShow 53 | LanDevPingRouter 54 | RouterPingWanDev 55 | LanDevPingWanDev 56 | Webserver_Download 57 | UciShowWireless 58 | WiFiOnOffCycle 59 | RestartNetwork 60 | iPerfBiDirTest 61 | Set_IPv6_Addresses 62 | LanDevPing6Router 63 | LanDevPing6WanDev 64 | iPerfBiDirTestIPV6 65 | 66 | [long] 67 | RootFSBootTest 68 | OpenwrtVersion 69 | OpkgList 70 | KernelModules 71 | MemoryUse 72 | InterfacesShow 73 | LanDevPingRouter 74 | RouterPingWanDev 75 | LanDevPingWanDev 76 | FirewallOFF 77 | FirewallON 78 | NetperfUdpTest 79 | SshWanDetect 80 | UciShow 81 | UCIPersists 82 | UciShowWireless 83 | WiFiOnOffCycle5 84 | WiFiOnOffCycle20 85 | TurnOnWifi 86 | WlanSetSSID 87 | WlanSetSSID_WPA2PSK 88 | IGMPv3_Running 89 | IGMPv3_Config 90 | IGMPv3_StopStart 91 | iPerfNonRoutedTest 92 | iPerfReverseTest 93 | iPerfBiDirTest 94 | iPerfTest 95 | iPerfNonRoutedTest 96 | iPerfReverseTest 97 | iPerfBiDirTest 98 | iPerfUDPReverseTest 99 | iPerfUDPBiDirTest 100 | BridgedMode 101 | 102 | [reboottentimes] 103 | RootFSBootTest 104 | OpenwrtVersion 105 | AnySbInstalled 106 | SB_EnableIfNot 107 | RebootHard 108 | MemoryUse 109 | RebootHard 110 | MemoryUse 111 | RebootHard 112 | MemoryUse 113 | RebootHard 114 | MemoryUse 115 | RebootHard 116 | MemoryUse 117 | RebootHard 118 | MemoryUse 119 | RebootHard 120 | MemoryUse 121 | RebootHard 122 | MemoryUse 123 | RebootHard 124 | MemoryUse 125 | RebootHard 126 | MemoryUse 127 | -------------------------------------------------------------------------------- /testsuites.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 2 | # 3 | # All rights reserved. 4 | # 5 | # This file is distributed under the Clear BSD license. 6 | # The full text can be found in LICENSE in the root directory. 7 | 8 | 9 | # Read simple test suite config files 10 | import config 11 | import devices.configreader 12 | tmp = devices.configreader.TestsuiteConfigReader() 13 | tmp.read(config.testsuite_config_files) 14 | list_tests = tmp.section 15 | 16 | # Create long or complicated test suites at run time. 17 | # key = suite name, value = list of tests names (strings) 18 | new_tests = {} 19 | 20 | # Combine simple and dynamic dictionary of test suites 21 | list_tests.update(new_tests) 22 | --------------------------------------------------------------------------------