test: update example and unit tests with new import roles:
tiny_test_fw is a python package now. import it using normal way.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
Internal use only.
|
||||
|
||||
This file provide method to control programmable attenuator.
|
||||
"""
|
||||
|
||||
import time
|
||||
import serial
|
||||
import codecs
|
||||
|
||||
|
||||
def set_att(port, att, att_fix=False):
|
||||
"""
|
||||
set attenuation value on the attenuator
|
||||
|
||||
:param port: serial port for attenuator
|
||||
:param att: attenuation value we want to set
|
||||
:param att_fix: fix the deviation with experience value
|
||||
:return: True or False
|
||||
"""
|
||||
|
||||
assert 0 <= att <= 62
|
||||
# fix att
|
||||
if att_fix:
|
||||
if att >= 33 and (att - 30 + 1) % 4 == 0:
|
||||
att_t = att - 1
|
||||
elif att >= 33 and (att - 30) % 4 == 0:
|
||||
att_t = att + 1
|
||||
else:
|
||||
att_t = att
|
||||
else:
|
||||
att_t = att
|
||||
|
||||
serial_port = serial.Serial(port, baudrate=9600, rtscts=False, timeout=0.1)
|
||||
if serial_port.isOpen() is False:
|
||||
raise IOError("attenuator control, failed to open att port")
|
||||
|
||||
cmd_hex = "7e7e10{:02x}{:x}".format(att_t, 0x10 + att_t)
|
||||
exp_res_hex = "7e7e20{:02x}00{:x}".format(att_t, 0x20 + att_t)
|
||||
|
||||
cmd = codecs.decode(cmd_hex, "hex")
|
||||
exp_res = codecs.decode(exp_res_hex, "hex")
|
||||
|
||||
serial_port.write(cmd)
|
||||
res = b""
|
||||
|
||||
for i in range(5):
|
||||
res += serial_port.read(20)
|
||||
if res == exp_res:
|
||||
result = True
|
||||
break
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
result = False
|
||||
|
||||
serial_port.close()
|
||||
return result
|
||||
@@ -0,0 +1,50 @@
|
||||
# Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import matplotlib
|
||||
# fix can't draw figure with docker
|
||||
matplotlib.use('Agg')
|
||||
import matplotlib.pyplot as plt # noqa: E402 - matplotlib.use('Agg') need to be before this
|
||||
|
||||
|
||||
# candidate colors
|
||||
LINE_STYLE_CANDIDATE = ['b-o', 'r-o', 'k-o', 'm-o', 'c-o', 'g-o', 'y-o',
|
||||
'b-s', 'r-s', 'k-s', 'm-s', 'c-s', 'g-s', 'y-s']
|
||||
|
||||
|
||||
def draw_line_chart(file_name, title, x_label, y_label, data_list):
|
||||
"""
|
||||
draw line chart and save to file.
|
||||
|
||||
:param file_name: abs/relative file name to save chart figure
|
||||
:param title: chart title
|
||||
:param x_label: x-axis label
|
||||
:param y_label: y-axis label
|
||||
:param data_list: a list of line data.
|
||||
each line is a dict of ("x-axis": list, "y-axis": list, "label": string)
|
||||
"""
|
||||
|
||||
plt.figure(figsize=(12, 6))
|
||||
plt.grid(True)
|
||||
|
||||
for i, data in enumerate(data_list):
|
||||
plt.plot(data["x-axis"], data["y-axis"], LINE_STYLE_CANDIDATE[i], label=data["label"])
|
||||
|
||||
plt.xlabel(x_label)
|
||||
plt.ylabel(y_label)
|
||||
plt.legend(fontsize=12)
|
||||
plt.title(title)
|
||||
plt.tight_layout(pad=3, w_pad=3, h_pad=3)
|
||||
plt.savefig(file_name)
|
||||
plt.close()
|
||||
@@ -0,0 +1,95 @@
|
||||
# Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
Internal use only.
|
||||
|
||||
This file implements controlling APC PDU via telnet.
|
||||
"""
|
||||
|
||||
import telnetlib
|
||||
|
||||
|
||||
class Control(object):
|
||||
""" control APC via telnet """
|
||||
|
||||
@classmethod
|
||||
def apc_telnet_make_choice(cls, telnet, choice):
|
||||
""" select a choice """
|
||||
telnet.read_until(b"Event Log")
|
||||
telnet.read_until(b">")
|
||||
telnet.write(choice.encode() + b"\r\n")
|
||||
|
||||
@classmethod
|
||||
def apc_telnet_common_action(cls, telnet, check_str, action):
|
||||
""" wait until a pattern and then write a line """
|
||||
telnet.read_until(check_str.encode())
|
||||
telnet.write(action.encode() + b"\r\n")
|
||||
|
||||
@classmethod
|
||||
def control(cls, apc_ip, control_dict):
|
||||
"""
|
||||
control APC
|
||||
|
||||
:param apc_ip: IP of APC
|
||||
:param control_dict: dict with outlet ID and "ON" or "OFF"
|
||||
"""
|
||||
|
||||
for _outlet in control_dict:
|
||||
assert 0 < _outlet < 9
|
||||
assert control_dict[_outlet] in ["ON", "OFF"]
|
||||
|
||||
# telnet
|
||||
# set timeout as 2s so that it won't waste time even can't access APC
|
||||
tn = telnetlib.Telnet(host=apc_ip, timeout=5)
|
||||
# log on
|
||||
cls.apc_telnet_common_action(tn, "User Name :", "apc")
|
||||
cls.apc_telnet_common_action(tn, "Password :", "apc")
|
||||
# go to Device Manager
|
||||
cls.apc_telnet_make_choice(tn, "1")
|
||||
# go to Outlet Management
|
||||
cls.apc_telnet_make_choice(tn, "2")
|
||||
# go to Outlet Control/Configuration
|
||||
cls.apc_telnet_make_choice(tn, "1")
|
||||
|
||||
# do select Outlet and control
|
||||
for _outlet in control_dict:
|
||||
# choose Outlet
|
||||
cls.apc_telnet_make_choice(tn, str(_outlet))
|
||||
# choose Control Outlet
|
||||
cls.apc_telnet_make_choice(tn, "1")
|
||||
# choose action
|
||||
_action = control_dict[_outlet]
|
||||
if "ON" in _action:
|
||||
cls.apc_telnet_make_choice(tn, "1")
|
||||
else:
|
||||
cls.apc_telnet_make_choice(tn, "2")
|
||||
# do confirm
|
||||
cls.apc_telnet_common_action(tn, "cancel :", "YES")
|
||||
cls.apc_telnet_common_action(tn, "continue...", "")
|
||||
# return to Outlet Control/Configuration
|
||||
cls.apc_telnet_make_choice(tn, "\033")
|
||||
cls.apc_telnet_make_choice(tn, "\033")
|
||||
|
||||
# exit to main menu and logout
|
||||
tn.write(b"\033\r\n")
|
||||
tn.write(b"\033\r\n")
|
||||
tn.write(b"\033\r\n")
|
||||
tn.write(b"4\r\n")
|
||||
|
||||
@classmethod
|
||||
def control_rest(cls, apc_ip, outlet, action):
|
||||
outlet_list = list(range(1, 9)) # has to be a list if we want to remove from it under Python 3
|
||||
outlet_list.remove(outlet)
|
||||
cls.control(apc_ip, dict.fromkeys(outlet_list, action))
|
||||
@@ -0,0 +1,245 @@
|
||||
"""
|
||||
this module generates markdown format test report for throughput test.
|
||||
|
||||
The test report contains 2 parts:
|
||||
|
||||
1. throughput with different configs
|
||||
2. throughput with RSSI
|
||||
"""
|
||||
import os
|
||||
|
||||
|
||||
class ThroughputForConfigsReport(object):
|
||||
THROUGHPUT_TYPES = ["tcp_tx", "tcp_rx", "udp_tx", "udp_rx"]
|
||||
|
||||
REPORT_FILE_NAME = "ThroughputForConfigs.md"
|
||||
|
||||
def __init__(self, output_path, ap_ssid, throughput_results, sdkconfig_files):
|
||||
"""
|
||||
:param ap_ssid: the ap we expected to use
|
||||
:param throughput_results: config with the following type::
|
||||
{
|
||||
"config_name": {
|
||||
"tcp_tx": result,
|
||||
"tcp_rx": result,
|
||||
"udp_tx": result,
|
||||
"udp_rx": result,
|
||||
},
|
||||
"config_name2": {},
|
||||
}
|
||||
"""
|
||||
self.output_path = output_path
|
||||
self.ap_ssid = ap_ssid
|
||||
self.results = throughput_results
|
||||
self.sdkconfigs = dict()
|
||||
for config_name in sdkconfig_files:
|
||||
self.sdkconfigs[config_name] = self._parse_config_file(sdkconfig_files[config_name])
|
||||
if not os.path.exists(output_path):
|
||||
os.makedirs(output_path)
|
||||
self.sort_order = self.sdkconfigs.keys()
|
||||
self.sort_order.sort()
|
||||
|
||||
@staticmethod
|
||||
def _parse_config_file(config_file_path):
|
||||
sdkconfig = {}
|
||||
with open(config_file_path, "r") as f:
|
||||
for line in f:
|
||||
if not line.isspace():
|
||||
if line[0] == "#":
|
||||
continue
|
||||
name, value = line.split("=")
|
||||
value = value.strip("\r\n")
|
||||
sdkconfig[name] = value if value else "n"
|
||||
return sdkconfig
|
||||
|
||||
def _generate_the_difference_between_configs(self):
|
||||
"""
|
||||
generate markdown list for different configs::
|
||||
|
||||
default: esp-idf default
|
||||
low:
|
||||
* `config name 1`: old value -> new value
|
||||
* `config name 2`: old value -> new value
|
||||
* ...
|
||||
...
|
||||
|
||||
"""
|
||||
|
||||
data = "## Config Definition:\r\n\r\n"
|
||||
|
||||
def find_difference(base, new):
|
||||
_difference = {}
|
||||
all_configs = set(base.keys())
|
||||
all_configs.update(set(new.keys()))
|
||||
for _config in all_configs:
|
||||
try:
|
||||
_base_value = base[_config]
|
||||
except KeyError:
|
||||
_base_value = "null"
|
||||
try:
|
||||
_new_value = new[_config]
|
||||
except KeyError:
|
||||
_new_value = "null"
|
||||
if _base_value != _new_value:
|
||||
_difference[_config] = "{} -> {}".format(_base_value, _new_value)
|
||||
return _difference
|
||||
|
||||
for i, _config_name in enumerate(self.sort_order):
|
||||
current_config = self.sdkconfigs[_config_name]
|
||||
|
||||
if i > 0:
|
||||
previous_config_name = self.sort_order[i - 1]
|
||||
previous_config = self.sdkconfigs[previous_config_name]
|
||||
else:
|
||||
previous_config = previous_config_name = None
|
||||
|
||||
if previous_config:
|
||||
# log the difference
|
||||
difference = find_difference(previous_config, current_config)
|
||||
data += "* {} (compared to {}):\r\n".format(_config_name, previous_config_name)
|
||||
for diff_name in difference:
|
||||
data += " * `{}`: {}\r\n".format(diff_name, difference[diff_name])
|
||||
return data
|
||||
|
||||
def _generate_report_for_one_type(self, throughput_type):
|
||||
"""
|
||||
generate markdown table with the following format::
|
||||
|
||||
| config name | throughput (Mbps) | free heap size (bytes) |
|
||||
|-------------|-------------------|------------------------|
|
||||
| default | 32.11 | 147500 |
|
||||
| low | 32.11 | 147000 |
|
||||
| medium | 33.22 | 120000 |
|
||||
| high | 43.11 | 100000 |
|
||||
| max | 45.22 | 79000 |
|
||||
"""
|
||||
empty = True
|
||||
|
||||
ret = "\r\n### {} {}\r\n\r\n".format(*throughput_type.split("_"))
|
||||
ret += "| config name | throughput (Mbps) | free heap size (bytes) |\r\n"
|
||||
ret += "|-------------|-------------------|------------------------|\r\n"
|
||||
for config in self.sort_order:
|
||||
try:
|
||||
result = self.results[config][throughput_type]
|
||||
throughput = "{:.02f}".format(max(result.throughput_by_att[self.ap_ssid].values()))
|
||||
heap_size = str(result.heap_size)
|
||||
# although markdown table will do alignment
|
||||
# do align here for better text editor presentation
|
||||
ret += "| {:<12}| {:<18}| {:<23}|\r\n".format(config, throughput, heap_size)
|
||||
empty = False
|
||||
except KeyError:
|
||||
pass
|
||||
return ret if not empty else ""
|
||||
|
||||
def generate_report(self):
|
||||
data = "# Throughput for different configs\r\n"
|
||||
data += "\r\nAP: {}\r\n".format(self.ap_ssid)
|
||||
|
||||
for throughput_type in self.THROUGHPUT_TYPES:
|
||||
data += self._generate_report_for_one_type(throughput_type)
|
||||
data += "\r\n------\r\n"
|
||||
|
||||
data += self._generate_the_difference_between_configs()
|
||||
|
||||
with open(os.path.join(self.output_path, self.REPORT_FILE_NAME), "w") as f:
|
||||
f.write(data)
|
||||
|
||||
|
||||
class ThroughputVsRssiReport(object):
|
||||
|
||||
REPORT_FILE_NAME = "ThroughputVsRssi.md"
|
||||
|
||||
def __init__(self, output_path, throughput_results):
|
||||
"""
|
||||
:param throughput_results: config with the following type::
|
||||
{
|
||||
"tcp_tx": result,
|
||||
"tcp_rx": result,
|
||||
"udp_tx": result,
|
||||
"udp_rx": result,
|
||||
}
|
||||
"""
|
||||
self.output_path = output_path
|
||||
self.raw_data_path = os.path.join(output_path, "raw_data")
|
||||
self.results = throughput_results
|
||||
self.throughput_types = self.results.keys()
|
||||
self.throughput_types.sort()
|
||||
if not os.path.exists(self.raw_data_path):
|
||||
os.makedirs(self.raw_data_path)
|
||||
|
||||
def _generate_summary(self):
|
||||
"""
|
||||
generate summary with the following format::
|
||||
|
||||
| item | curve analysis | max throughput (Mbps) |
|
||||
|---------|----------------|-----------------------|
|
||||
| tcp tx | Success | 32.11 |
|
||||
| tcp rx | Success | 32.11 |
|
||||
| udp tx | Success | 45.22 |
|
||||
| udp rx | Failed | 55.44 |
|
||||
"""
|
||||
|
||||
ret = "\r\n### Summary\r\n\r\n"
|
||||
ret += "| item | curve analysis | max throughput (Mbps) |\r\n"
|
||||
ret += "|---------|----------------|-----------------------|\r\n"
|
||||
|
||||
for _type in self.throughput_types:
|
||||
result = self.results[_type]
|
||||
max_throughput = 0.0
|
||||
curve_analysis = "Failed" if result.error_list else "Success"
|
||||
for ap_ssid in result.throughput_by_att:
|
||||
_max_for_ap = max(result.throughput_by_rssi[ap_ssid].values())
|
||||
if _max_for_ap > max_throughput:
|
||||
max_throughput = _max_for_ap
|
||||
max_throughput = "{:.02f}".format(max_throughput)
|
||||
ret += "| {:<8}| {:<15}| {:<22}|\r\n".format("{}_{}".format(result.proto, result.direction),
|
||||
curve_analysis, max_throughput)
|
||||
return ret
|
||||
|
||||
def _generate_report_for_one_type(self, result):
|
||||
"""
|
||||
generate markdown table with the following format::
|
||||
|
||||
### tcp rx
|
||||
|
||||
Errors:
|
||||
|
||||
* detected error 1
|
||||
* ...
|
||||
|
||||
AP: ap_ssid
|
||||
|
||||

|
||||
|
||||
AP: ap_ssid
|
||||
|
||||

|
||||
|
||||
"""
|
||||
result.post_analysis()
|
||||
ret = "\r\n### {} {}\r\n".format(result.proto, result.direction)
|
||||
if result.error_list:
|
||||
ret += "\r\nErrors:\r\n\r\n"
|
||||
for error in result.error_list:
|
||||
ret += "* " + error + "\r\n"
|
||||
for ap_ssid in result.throughput_by_rssi:
|
||||
ret += "\r\nAP: {}\r\n".format(ap_ssid)
|
||||
# draw figure
|
||||
file_name = result.draw_throughput_figure(self.raw_data_path, ap_ssid, "rssi")
|
||||
result.draw_throughput_figure(self.raw_data_path, ap_ssid, "att")
|
||||
result.draw_rssi_vs_att_figure(self.raw_data_path, ap_ssid)
|
||||
|
||||
ret += "\r\n\r\n".format(os.path.join("raw_data", file_name))
|
||||
|
||||
return ret
|
||||
|
||||
def generate_report(self):
|
||||
data = "# Throughput Vs RSSI\r\n"
|
||||
|
||||
data += self._generate_summary()
|
||||
|
||||
for _type in self.throughput_types:
|
||||
data += self._generate_report_for_one_type(self.results[_type])
|
||||
|
||||
with open(os.path.join(self.output_path, self.REPORT_FILE_NAME), "w") as f:
|
||||
f.write(data)
|
||||
Reference in New Issue
Block a user