Update for v1.0rc1

This commit is contained in:
James Deng 2024-04-30 17:48:07 +08:00
parent 91a5524b15
commit 5a98e3422f
40 changed files with 1117 additions and 41 deletions

View file

@ -13,9 +13,11 @@
```
├── cricket # Cricket is a graphical tool that helps you run your test suites.
├── gui-main
└── tests # factory test case
├── auto # auto test case
└── manual # manual test case
├── res # test case resources
├─── tests # factory test case
│  ├── auto # auto test case
│  └── manual # manual test case
└── utils # common files
```
## 测试项
@ -25,12 +27,28 @@
添加测试项规则:
- 自动测试项和手动测试项分别添加到`tests/auto``tests/manual`目录
- 每个模块一个文件,以`test_`开头,文件里定义一个类,继承`unittest.TestCase`
- 测试项为类的方法,方法名`test_`开头
- 每个模块一个文件,以`test_`开头,可以加上序号规定加载顺序,例如`test_01_`文件里定义一个测试类,继承`unittest.TestCase`
- 测试项为类的方法,测试方法的名称必须`test_`开头
- 类里定义一个字典`LANGUAGES`,用于支持多国语音
注意事项:
- 不要在测试方法里调用`os._exit()``sys.exit()``QApplication quit()`等方法,会导致测试中止,建议创建线程或子进程。
## 多国语言
相关文件:
- cricket/cricket/lang.py
- cricket/cricket/languages.json
语言:
- zh中文
- en英文
默认语言:中文,可以通过`cricket/cricket/lang.py`的_current_lang修改。
## TODO
- 支持命令行
- 支持命令行

View file

@ -6,13 +6,16 @@
"stop_button": "停止",
"quit_button": "退出",
"reboot_button": "重启",
"poweroff_button": "关闭",
"poweroff_button": "关机",
"camera": "摄像头",
"audio": "音频",
"test_table_head": ["模块", "测试项", "结果"],
"test_step": "测试步骤",
"start_button": "开始",
"play_button": "播放",
"record_button": "录音",
"playback_button": "回放",
"speed_button": "速度",
"pass_button": "通过",
"fail_button": "失败"
},
@ -24,12 +27,15 @@
"quit_button": "Quit",
"reboot_button": "Reboot",
"poweroff_button": "Shutdown",
"camera": "Camera",
"audio": "Microphone",
"test_table_head": ["Module", "Test Item", "Result"],
"test_step": "Test Step",
"start_button": "Start",
"play_button": "Play",
"record_button": "Record",
"playback_button": "Playback",
"speed_button": "Speed",
"pass_button": "Pass",
"fail_button": "Fail"
}

View file

@ -3,7 +3,7 @@
This is the "View" of the MVC world.
"""
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtCore import Qt, QTimer, QUrl
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import (
QMainWindow,
@ -14,10 +14,16 @@ from PyQt5.QtWidgets import (
QGroupBox,
QTableWidget,
QStatusBar,
QTableWidgetItem
QTableWidgetItem,
QHeaderView
)
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtMultimediaWidgets import QVideoWidget
import os
import time
import threading
import subprocess
from importlib import import_module
from cricket.model import TestMethod, TestCase, TestModule
@ -28,7 +34,7 @@ from cricket.lang import SimpleLang
# Display constants for test status
STATUS = {
TestMethod.STATUS_PASS: {
'description': u'Pass',
'description': u'通过',
'symbol': u'\u25cf',
'tag': 'pass',
'color': '#28C025',
@ -40,7 +46,7 @@ STATUS = {
'color': '#259EBF'
},
TestMethod.STATUS_FAIL: {
'description': u'Failure',
'description': u'失败',
'symbol': u'F',
'tag': 'fail',
'color': '#E32C2E'
@ -87,6 +93,8 @@ class MainWindow(QMainWindow, SimpleLang):
self.setWindowTitle(self.get_text('title'))
# self.showFullScreen()
self.font_size = 16
# Set up the main content for the window.
self._setup_main_content()
@ -107,9 +115,12 @@ class MainWindow(QMainWindow, SimpleLang):
It is a persistent GUI component
'''
self.setStyleSheet(f'font-size: {self.font_size}px;')
self.content = QFrame(self)
self.content_layout = QVBoxLayout(self.content)
# toolbar
toolbar = QFrame(self.content)
layout = QGridLayout(toolbar)
@ -139,19 +150,67 @@ class MainWindow(QMainWindow, SimpleLang):
self.content_layout.addWidget(toolbar)
# tests
# -------------------
# | | |
# | auto | camera |
# | | |
# -------------------
# | manual | audio |
# | | |
# -------------------
self.tests = QFrame(self.content)
self.tests_layout = QGridLayout(self.tests)
self._setup_test_table('auto', 0, 0, 4, 1)
self._setup_test_table('manual', 4, 0, 3, 1)
camera_box = QGroupBox(self.get_text('camera'), self.tests)
camera_box_layout = QVBoxLayout(camera_box)
video_widget = QVideoWidget(camera_box)
self.media_player = QMediaPlayer()
self.media_player.setVideoOutput(video_widget)
camera_box_layout.addWidget(video_widget)
audio_box = QGroupBox(self.get_text('audio'), self.tests)
audio_box_layout = QVBoxLayout(audio_box)
self.tests_layout.addWidget(camera_box, 0, 1, 4, 1)
self.tests_layout.addWidget(audio_box, 4, 1, 3, 1)
self.tests_layout.setRowStretch(0, 4)
self.tests_layout.setRowStretch(1, 4)
self.tests_layout.setRowStretch(2, 4)
self.tests_layout.setRowStretch(3, 4)
self.tests_layout.setRowStretch(4, 3)
self.tests_layout.setRowStretch(5, 3)
self.tests_layout.setRowStretch(6, 3)
self.tests_layout.setColumnStretch(0, 1)
self.tests_layout.setColumnStretch(1, 1)
self.content_layout.addWidget(self.tests)
# set main content to window
self.setCentralWidget(self.content)
def _setup_test_table(self, name):
def _setup_test_table(self, name, row, column, row_span, column_span):
module = import_module(name)
box = QGroupBox(module.MODULE_NAME[self.current_lang], self.content)
box = QGroupBox(module.MODULE_NAME[self.current_lang], self.tests)
box.setStyleSheet("QGroupBox::title { font-weight: bold; }")
layout = QVBoxLayout(box)
columns = self.get_text('test_table_head')
table = QTableWidget(box)
table.setStyleSheet('QTableWidget { background-color: black; color: white; }')
table.setColumnCount(len(columns))
table.setHorizontalHeaderLabels(columns)
for i in range(len(columns)):
table.horizontalHeader().setSectionResizeMode(i, QHeaderView.Stretch)
table.verticalHeader().setStyleSheet('QHeaderView::section { width: 32px; }')
table.setSelectionBehavior(QTableWidget.SelectRows)
table.itemSelectionChanged.connect(self.on_testMethodSelected)
layout.addWidget(table)
@ -162,7 +221,7 @@ class MainWindow(QMainWindow, SimpleLang):
layout.addWidget(status)
self.run_status[name] = status
self.content_layout.addWidget(box)
self.tests_layout.addWidget(box, row, column, row_span, column_span)
######################################################
# Handlers for setting a new project
@ -201,9 +260,15 @@ class MainWindow(QMainWindow, SimpleLang):
item = QTableWidgetItem(self._get_text(subModuleName, subModule, testMethod_name))
table.setItem(row, 1, item)
item = QTableWidgetItem('')
item.setTextAlignment(Qt.AlignCenter)
item.setData(Qt.UserRole, testMethod.path)
table.setItem(row, 2, item)
logical_dpi = table.logicalDpiY()
font_pixel = self.font_size * logical_dpi / 72
row_height = int(font_pixel * 2)
table.setRowHeight(row, row_height)
@project.setter
def project(self, project):
self._project = project
@ -215,7 +280,6 @@ class MainWindow(QMainWindow, SimpleLang):
# Populate the initial tree nodes. This is recursive, because
# the tree could be of arbitrary depth.
for testModule_name, testModule in sorted(project.items()):
self._setup_test_table(testModule_name)
self._add_test_module(testModule_name, testModule)
self.executor[testModule_name] = None
@ -228,18 +292,72 @@ class MainWindow(QMainWindow, SimpleLang):
######################################################
def mainloop(self):
pipeline = 'gst-pipeline: spacemitsrc location=/opt/factorytest/res/camtest_sensor0_mode0.json close-dmabuf=1 ! videoconvert ! video/x-raw,format=BGRx ! autovideosink sync=0'
self.media_player.setMedia(QMediaContent(QUrl(pipeline)))
self.media_player.play()
self.audio_thread = threading.Thread(target=lambda: self.audio_loop())
self.audio_thread.start()
self.cmd_run_all()
self.root.exec_()
def _play_wav(self, device, volume, path):
cmd = f'amixer -c 1 cset numid=1,iface=MIXER,name="DAC Playback Volume" {volume}'
proc = subprocess.run(cmd, shell=True, capture_output=True, text=True)
print(f'Set playback volume to {volume} return {proc.returncode}')
cmd = f'aplay -D{device} -r 48000 -f S16_LE {path}'
proc = subprocess.run(cmd, shell=True, capture_output=True, text=True)
print(f'Play {path} on {device} return {proc.returncode}')
def _record_wav(self, device, volume, duration, path):
cmd = f'amixer -c 1 cset numid=1,iface=MIXER,name="ADC Capture Volume" {volume},{volume}'
proc = subprocess.run(cmd, shell=True, capture_output=True, text=True)
print(f'Set capture volume to {volume} return {proc.returncode}')
cmd = f'arecord -D{device} -r 48000 -f S16_LE -d {duration} {path}'
proc = subprocess.run(cmd, shell=True, capture_output=True, text=True)
print(f'Record {path} on {device} in {duration}s return {proc.returncode}')
def audio_loop(self):
# sleep for a while
time.sleep(15)
device = 'hw:1'
playback_volume = 184
record_volume = 184
duration = 5
res_path = '/opt/factorytest/res'
start_record_file = f'{res_path}/start-record.wav'
stop_record_file = f'{res_path}/stop-record.wav'
start_play_file = f'{res_path}/start-play.wav'
stop_play_file = f'{res_path}/stop-play.wav'
record_file = '/tmp/factorytest-record.wav'
while True:
self._play_wav(device, playback_volume, start_record_file)
self._record_wav(device, record_volume, duration, record_file)
self._play_wav(device, playback_volume, stop_record_file)
self._play_wav(device, playback_volume, start_play_file)
self._play_wav(device, playback_volume, record_file)
self._play_wav(device, playback_volume, stop_play_file)
time.sleep(duration)
######################################################
# User commands
######################################################
def cmd_poweroff(self):
self.media_player.stop()
self.stop()
# self.root.quit()
os.system('poweroff')
def cmd_reboot(self):
self.media_player.stop()
self.stop()
# self.root.quit()
os.system('reboot')
@ -298,8 +416,9 @@ class MainWindow(QMainWindow, SimpleLang):
if item.data(Qt.UserRole) == node.path:
for column in range(columnCount):
_item = table.item(row, column)
_item.setForeground(QColor(STATUS[node.status]['color']))
item.setText(STATUS[node.status]['description'])
_item.setBackground(QColor(STATUS[node.status]['color']))
if module == 'auto':
item.setText(STATUS[node.status]['description'])
break
def on_testProgress(self, executor):
@ -386,12 +505,12 @@ class MainWindow(QMainWindow, SimpleLang):
# 'skip': self.executor.result_count.get(TestMethod.STATUS_SKIP, 0),
# })
# Reset the buttons
self.reset_button_states_on_end()
# Drop the reference to the executor
self.executor[module] = None
# Reset the buttons
self.reset_button_states_on_end()
def on_executorSuiteError(self, event, module, error):
"An error occurred running the test suite."
# Display the error in a dialog

145
deprecated/test_01_lcd.py Normal file
View file

@ -0,0 +1,145 @@
from unittest import TestCase
import subprocess
import threading
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QFrame,
QVBoxLayout,
QGroupBox,
QLabel,
QStatusBar,
QPushButton,
QGridLayout
)
from cricket.lang import SimpleLang
result = False
class LCDTestWindow(SimpleLang):
def __init__(self, languages) -> None:
super().__init__()
self.languages = languages
self.app = QApplication([])
self.window = QMainWindow()
self.window.setWindowTitle(self._get_text('title'))
# self.window.setWindowFlags(Qt.WindowStaysOnTopHint)
# Main content
content = QFrame(self.window)
content_layout = QVBoxLayout(content)
# Test step
box = QGroupBox(self.get_text('test_step'), content)
box_layout = QVBoxLayout(box)
label = QLabel(self._get_text('test_step'), box)
box_layout.addWidget(label)
content_layout.addWidget(box)
# Status
# self.status = QStatusBar(content)
# self.status.showMessage('Not running')
# content_layout.addWidget(self.status)
# Toolbar
toolbar = QFrame(content)
toolbar_layout = QGridLayout(toolbar)
self.pass_button = QPushButton(self.get_text('pass_button'), toolbar)
# self.pass_button.setDisabled(True)
self.pass_button.clicked.connect(self.cmd_pass)
self.pass_button.setFocus()
toolbar_layout.addWidget(self.pass_button, 0, 0)
self.fail_button = QPushButton(self.get_text('fail_button'), toolbar)
# self.fail_button.setDisabled(True)
self.fail_button.clicked.connect(self.cmd_fail)
toolbar_layout.addWidget(self.fail_button, 0, 1)
content_layout.addWidget(toolbar)
self.window.setCentralWidget(content)
# Move to center
# screen = QGuiApplication.primaryScreen()
# if screen:
# screen_geometry = screen.geometry()
# window_geometry = self.window.geometry()
# x = (screen_geometry.width() - window_geometry.width()) // 2
# y = (screen_geometry.height() - window_geometry.height()) // 2
# self.window.move(x, y)
# self.window.show()
self.window.showFullScreen()
self.app.exec_()
def _get_text(self, key):
if self.languages:
lang = self.languages.get(self.current_lang)
if lang is not None:
text = lang.get(key)
if text is not None:
return text
return 'Undefined'
def cmd_pass(self):
global result
# self.status.showMessage('Stopping...')
result = True
self.app.quit()
def cmd_fail(self):
global result
# self.status.showMessage('Stopping...')
result = False
self.app.quit()
class LCDTest(TestCase):
LANGUAGES = {
'zh': {
'LCDTest': 'MIPI屏',
'test_lcd': '彩色条纹',
'title': 'MIPI屏',
'test_step': '''1. 观察MIPI屏彩色条纹是否符合预期
'''
},
'en': {
'LCDTest': 'MIPI Screen',
'test_lcd': 'Color Bar',
'title': 'MIPI Screen',
'test_step': '''1. 观察MIPI屏彩色条纹是否符合预期
'''
}
}
def test_lcd(self):
global result
t = threading.Thread(target=LCDTestWindow, args=(self.LANGUAGES,))
t.start()
cmd = 'modetest -M spacemit -s 136@127:1200x1920 -a -P 31@127:1200x1920+0+0@AR24'
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
t.join()
if proc.poll() is None:
print('Try to kill process...')
proc.kill()
self.assertTrue(result)

152
deprecated/test_03_fan.py Normal file
View file

@ -0,0 +1,152 @@
from unittest import TestCase
import threading
import os
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QFrame,
QVBoxLayout,
QGroupBox,
QLabel,
QStatusBar,
QPushButton,
QGridLayout
)
from cricket.lang import SimpleLang
result = False
class FanTestWindow(SimpleLang):
def __init__(self, speed_routine, languages) -> None:
super().__init__()
self.speed_routine = speed_routine
self.languages = languages
self.app = QApplication([])
self.window = QMainWindow()
self.window.setWindowTitle(self._get_text('title'))
# self.window.setWindowFlags(Qt.WindowStaysOnTopHint)
# Main content
content = QFrame(self.window)
content_layout = QVBoxLayout(content)
# Test step
box = QGroupBox(self.get_text('test_step'), content)
box_layout = QVBoxLayout(box)
label = QLabel(self._get_text('test_step'), box)
box_layout.addWidget(label)
content_layout.addWidget(box)
# Status
# self.status = QStatusBar(content)
# self.status.showMessage('Not running')
# content_layout.addWidget(self.status)
# Toolbar
toolbar = QFrame(content)
toolbar_layout = QGridLayout(toolbar)
self.speed_button = QPushButton(self.get_text('speed_button'), toolbar)
self.speed_button.clicked.connect(self.cmd_speed)
self.speed_button.setFocus()
toolbar_layout.addWidget(self.speed_button, 0, 0)
self.pass_button = QPushButton(self.get_text('pass_button'), toolbar)
self.pass_button.setDisabled(True)
self.pass_button.clicked.connect(self.cmd_pass)
toolbar_layout.addWidget(self.pass_button, 0, 1)
self.fail_button = QPushButton(self.get_text('fail_button'), toolbar)
self.fail_button.setDisabled(True)
self.fail_button.clicked.connect(self.cmd_fail)
toolbar_layout.addWidget(self.fail_button, 0, 2)
content_layout.addWidget(toolbar)
self.window.setCentralWidget(content)
# Move to center
# screen = QGuiApplication.primaryScreen()
# if screen:
# screen_geometry = screen.geometry()
# window_geometry = self.window.geometry()
# x = (screen_geometry.width() - window_geometry.width()) // 2
# y = (screen_geometry.height() - window_geometry.height()) // 2
# self.window.move(x, y)
# self.window.show()
self.window.showFullScreen()
self.app.exec_()
def _get_text(self, key):
if self.languages:
lang = self.languages.get(self.current_lang)
if lang is not None:
text = lang.get(key)
if text is not None:
return text
return 'Undefined'
def cmd_speed(self):
self.speed_routine()
self.pass_button.setDisabled(False)
self.fail_button.setDisabled(False)
def cmd_pass(self):
global result
# self.status.showMessage('Stopping...')
result = True
self.app.quit()
def cmd_fail(self):
global result
# self.status.showMessage('Stopping...')
result = False
self.app.quit()
class FanTest(TestCase):
LANGUAGES = {
'zh': {
'FanTest': '风扇',
'test_speed': '变速',
'title': '风扇',
'test_step': '''1. 检查风扇是否正常转动
2. 点击变速检查风扇转速是否随之变化
'''
},
'en': {
'FanTest': 'Fan',
'test_speed': 'Speed',
'title': 'Fan',
'test_step': '''1. 检查风扇是否正常转动
2. 点击变速检查风扇转速是否随之变化
'''
}
}
def _speed_routine(self):
pass
def test_speed(self):
global result
t = threading.Thread(target=FanTestWindow, args=(self._speed_routine,
self.LANGUAGES,))
t.start()
t.join()
self.assertTrue(result)

182
deprecated/test_03_gpio.py Normal file
View file

@ -0,0 +1,182 @@
from unittest import TestCase
import threading
import os
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QFrame,
QVBoxLayout,
QGroupBox,
QLabel,
QStatusBar,
QPushButton,
QGridLayout
)
from cricket.lang import SimpleLang
result = False
class GPIOTestWindow(SimpleLang):
def __init__(self, languages) -> None:
super().__init__()
self.languages = languages
self.app = QApplication([])
self.window = QMainWindow()
self.window.setWindowTitle(self._get_text('title'))
# self.window.setWindowFlags(Qt.WindowStaysOnTopHint)
# Main content
content = QFrame(self.window)
content_layout = QVBoxLayout(content)
# Test step
box = QGroupBox(self.get_text('test_step'), content)
box_layout = QVBoxLayout(box)
label = QLabel(self._get_text('test_step'), box)
box_layout.addWidget(label)
content_layout.addWidget(box)
# Status
# self.status = QStatusBar(content)
# self.status.showMessage('Not running')
# content_layout.addWidget(self.status)
# Toolbar
toolbar = QFrame(content)
toolbar_layout = QGridLayout(toolbar)
self.pass_button = QPushButton(self.get_text('pass_button'), toolbar)
# self.pass_button.setDisabled(True)
self.pass_button.clicked.connect(self.cmd_pass)
self.pass_button.setFocus()
toolbar_layout.addWidget(self.pass_button, 0, 0)
self.fail_button = QPushButton(self.get_text('fail_button'), toolbar)
# self.fail_button.setDisabled(True)
self.fail_button.clicked.connect(self.cmd_fail)
toolbar_layout.addWidget(self.fail_button, 0, 1)
content_layout.addWidget(toolbar)
self.window.setCentralWidget(content)
# Move to center
# screen = QGuiApplication.primaryScreen()
# if screen:
# screen_geometry = screen.geometry()
# window_geometry = self.window.geometry()
# x = (screen_geometry.width() - window_geometry.width()) // 2
# y = (screen_geometry.height() - window_geometry.height()) // 2
# self.window.move(x, y)
# self.window.show()
self.window.showFullScreen()
self.app.exec_()
def _get_text(self, key):
if self.languages:
lang = self.languages.get(self.current_lang)
if lang is not None:
text = lang.get(key)
if text is not None:
return text
return 'Undefined'
def cmd_pass(self):
global result
# self.status.showMessage('Stopping...')
result = True
self.app.quit()
def cmd_fail(self):
global result
# self.status.showMessage('Stopping...')
result = False
self.app.quit()
class GPIOTest(TestCase):
LANGUAGES = {
'zh': {
'GPIOTest': 'GPIO',
'test_gpio': '开关灯',
'title': 'GPIO',
'test_step': '''1. 观察26 pin连接器灯是否全亮
'''
},
'en': {
'GPIOTest': 'GPIO',
'test_gpio': '开关灯',
'title': 'GPIO',
'test_step': '''1. 观察26 pin连接器灯是否全亮
'''
}
}
def _export(self, gpios):
export_file = '/sys/class/gpio/export'
self.assertTrue(os.path.exists(export_file))
for gpio in gpios:
try:
with open(export_file, 'w') as f:
f.write(gpio)
except:
self.fail(f'Export gpio{gpio} fail')
def _out(self, gpios, value):
for gpio in gpios:
try:
with open(f'/sys/class/gpio/gpio{gpio}/direction', 'w') as f:
f.write('out')
with open(f'/sys/class/gpio/gpio{gpio}/value', 'w') as f:
f.write(value)
except:
self.fail(f'Set gpio{gpio} fail')
def _unexport(self, gpios):
unexport_file = '/sys/class/gpio/unexport'
self.assertTrue(os.path.exists(unexport_file))
for gpio in gpios:
try:
with open(unexport_file, 'w') as f:
f.write(gpio)
except:
self.fail(f'Unexport gpio{gpio} fail')
def test_gpio(self):
global result
t = threading.Thread(target=GPIOTestWindow, args=(self.LANGUAGES,))
t.start()
# turn on
gpios = [
'47', '48', '49', '50', '51', '52',
'70', '71', '72', '73', '74',
'75', '76', '77', '78',
'90', '91', '92',
]
self._export(gpios)
self._out(gpios, '1')
t.join()
# turn off
self._out(gpios, '0')
self._unexport(gpios)
self.assertTrue(result)

View file

@ -31,7 +31,8 @@ class SpeakerTest(TestCase):
bell_file = '/opt/factorytest/res/nocturne.wav'
cmd = f'aplay -Dhw:1,0 -r 48000 -f S16_LE {bell_file} > /dev/null'
self.play_proc = subprocess.Popen(cmd, shell=True)
self.play_proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
def _stop_routine(self):
if self.play_proc.poll() is None:

View file

@ -34,7 +34,8 @@ class MicTest(TestCase):
timeout = 10
record_file = '/tmp/record.wav'
cmd = f'arecord -Dhw:1,0 -r 48000 -f S16_LE -d {timeout} {record_file}'
self.record_proc = subprocess.Popen(cmd, shell=True)
self.record_proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
def _stop_record(self):
if self.record_proc.poll() is None:
@ -48,7 +49,8 @@ class MicTest(TestCase):
print(f'playback amixer: {result.returncode}')
cmd = 'aplay -Dhw:1,0 -r 48000 -f S16_LE /tmp/record.wav'
self.playback_proc = subprocess.Popen(cmd, shell=True)
self.playback_proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
def _stop_playback(self):
if self.playback_proc.poll() is None:

View file

@ -33,7 +33,8 @@ class HeadphonesTest(TestCase):
bell_file = '/opt/factorytest/res/nocturne.wav'
cmd = f'aplay -Dhw:1,0 -r 48000 -f S16_LE {bell_file}'
self.play_proc = subprocess.Popen(cmd, shell=True)
self.play_proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
def _stop_routine(self):
if self.play_proc.poll() is None:

View file

@ -36,7 +36,8 @@ class MicrophoneTest(TestCase):
timeout = 10
record_file = '/tmp/record.wav'
cmd = f'arecord -Dhw:1,0 -r 48000 -f S16_LE -d {timeout} {record_file}'
self.record_proc = subprocess.Popen(cmd, shell=True)
self.record_proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
def _stop_record(self):
if self.record_proc.poll() is None:
@ -50,7 +51,8 @@ class MicrophoneTest(TestCase):
print(f'playback amixer: {result.returncode}')
cmd = 'aplay -Dhw:1,0 -r 48000 -f S16_LE /tmp/record.wav'
self.playback_proc = subprocess.Popen(cmd, shell=True)
self.playback_proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
def _stop_playback(self):
if self.playback_proc.poll() is None:

View file

@ -0,0 +1,19 @@
from unittest import TestCase
import os
class CameraTest(TestCase):
LANGUAGES = {
'zh': {
'CameraTest': '摄像头',
'test_still': '拍照',
},
'en': {
'CameraTest': 'Camera',
'test_still': 'Still',
}
}
def test_still(self):
self.assertTrue(os.path.exists('/tmp/cpp0_output_1920x1080_s1920.nv12'))
self.assertTrue(os.path.exists('/tmp/raw_output0_3840x2160.raw'))

21
memtester.sh Executable file
View file

@ -0,0 +1,21 @@
#!/bin/bash
test_size=100M
test_loop=1000
echo "Starting memtester..."
memtester $test_size $test_loop &
# Get the process ID of the memtester instance
memtester_pid=$!
# Wait for the process to finish
wait $memtester_pid
# Check the exit status of memtester process
if [ $? -eq 0 ]; then
echo "memtester test completed successfully."
else
echo "memtester test encountered an error or exited prematurely."
echo none > /sys/class/leds/sys-led/trigger
fi

View file

@ -0,0 +1,69 @@
{
"tuning_server_enable":1,
"show_fps":1,
"auto_run": 1,
"test_frame": 30,
"dump_one_frame": 29,
"cpp_node": [
{
"name": "cpp0",
"enable": 1,
"format":"NV12",
"src_from_file": 1,
"src_path":"/tmp/cpp_case_in_data/1920x1080/",
"size_width":1280,
"size_height":720,
},
{
"name": "cpp1",
"enable": 0,
"format":"NV12",
"src_from_file": 0,
"src_path":"/vendor/etc/camera/",
"size_width":1920,
"size_height":1080,
},
],
"isp_node":[
{
"name": "isp0",
"enable": 1,
"work_mode":"online",
"format":"NV12",
"out_width":1280,
"out_height":720,
"sensor_name":"ov16a10_spm",
"sensor_id" : 0,
"sensor_work_mode":0,
"fps":30,
"src_file":"/tmp/1920x1080_raw12_long_packed.vrf",
"bit_depth": 12,
"in_width":1920,
"in_height":1080,
},
{
"name": "isp1",
"enable": 0,
"work_mode":"offline_capture",
"format":"NV12",
"out_width":1600,
"out_height":1200,
"src_file":"/tmp/1920x1080_raw12_long_packed.vrf",
"bit_depth": 12,
"in_width":1920,
"in_height":1080,
"sensor_name":"gc2375h_spm",
"sensor_id" : 1,
"sensor_work_mode":0,
"fps":30,
},
]
}

BIN
res/canon.wav Normal file

Binary file not shown.

Binary file not shown.

BIN
res/start-play.wav Normal file

Binary file not shown.

BIN
res/start-record.wav Normal file

Binary file not shown.

BIN
res/stop-play.wav Normal file

Binary file not shown.

BIN
res/stop-record.wav Normal file

Binary file not shown.

8
stability Executable file
View file

@ -0,0 +1,8 @@
#!/bin/bash -e
ROOT=$(dirname "$(readlink -f "$0")")
pushd $ROOT > /dev/null
./stress-ng.sh &
./memtester.sh &
popd > /dev/null

18
stress-ng.sh Executable file
View file

@ -0,0 +1,18 @@
#!/bin/bash
echo "Starting stress test..."
stress-ng --cpu 6 --cpu-method all --cpu-load 50 --metrics-brief &
# Get the process ID of the stress-ng instance
stress_ng_pid=$!
# Wait for the process to finish
wait $stress_ng_pid
# Check the exit status of stress-ng process
if [ $? -eq 0 ]; then
echo "Stress test completed successfully."
else
echo "Stress test encountered an error or exited prematurely."
echo none > /sys/class/leds/sys-led/trigger
fi

19
tests/auto/test_01_ctp.py Normal file
View file

@ -0,0 +1,19 @@
from unittest import TestCase
import os
class CTPTest(TestCase):
LANGUAGES = {
'zh': {
'CTPTest': '触摸屏',
'test_read_model': '读取型号'
},
'en': {
'CTPTest': 'CTP',
'test_read_model': 'Read model'
}
}
def test_read_model(self):
model_file = '/sys/devices/platform/soc/d4018800.i2c/i2c-6/6-005d/productinfo'
self.assertTrue(os.path.exists(model_file))

View file

@ -0,0 +1,21 @@
from unittest import TestCase
import os
import subprocess
#import usb.core
class MiniPCIeTest(TestCase):
LANGUAGES = {
'zh': {
'MiniPCIeTest': 'Mini PCIe to SATA',
'test_read_model': '读取型号'
},
'en': {
'MiniPCIeTest': 'Mini PCIe to SATA',
'test_read_model': 'Read model'
}
}
def test_read_model(self):
model_file = '/sys/bus/pci/devices/0002:01:00.0/ata1/host0/target0:0:0/0:0:0:0/model'
self.assertTrue(os.path.exists(model_file))

View file

@ -19,6 +19,7 @@ class UDiskTest(TestCase):
u3_hub_dir = '/sys/devices/platform/soc/soc:usb3@0/c0a00000.dwc3/xhci-hcd.0.auto/usb3/3-1/'
self.assertTrue(os.path.exists(f'{u2_hub_dir}/product'))
self.assertTrue(os.path.exists(f'{u3_hub_dir}/product'))
# self.assertTrue(os.path.exists(f'{u3_hub_dir}/3-1.2/product'))
# self.assertTrue(os.path.exists(f'{u3_hub_dir}/3-1.3/product'))
self.assertTrue(os.path.exists(f'{u3_hub_dir}/3-1.4/product'))
self.assertTrue(os.path.exists(f'{u2_hub_dir}/2-1.1/product'))
self.assertTrue(os.path.exists(f'{u2_hub_dir}/2-1.3/product'))
self.assertTrue(os.path.exists(f'{u3_hub_dir}/3-1.2/product'))
self.assertTrue(os.path.exists(f'{u3_hub_dir}/3-1.4/product'))

View file

@ -0,0 +1,24 @@
from unittest import TestCase
import os
class EEPROMTest(TestCase):
LANGUAGES = {
'zh': {
'EEPROMTest': 'EEPROM',
'test_read': '读取数据'
},
'en': {
'EEPROMTest': 'EEPROM',
'test_read': 'Read data'
}
}
def test_read(self):
eeprom_file = '/sys/devices/platform/soc/d4012000.i2c/i2c-2/2-0050/eeprom'
self.assertTrue(os.path.exists(eeprom_file))
try:
with open(eeprom_file, 'rb') as f:
f.read(8)
except:
self.fail('Read fail')

View file

@ -1,12 +1,13 @@
from unittest import TestCase
import os
import time
import socket
import struct
import fcntl
import subprocess
from dns.resolver import Resolver
class Eth0Test(TestCase):
LANGUAGES = {
'zh': {
@ -35,8 +36,9 @@ class Eth0Test(TestCase):
def test_ping(self):
ifname = 'eth0'
ping = f'ping -I {ifname} -c 3 baidu.com'
timeout = 10
site = 'baidu.com'
ping = f'ping -I {ifname} -c 3 {site}'
timeout = 15
i = 0
while i < timeout:
@ -54,7 +56,11 @@ class Eth0Test(TestCase):
try:
ip = self.get_ip(ifname)
print(f'{ifname}: {ip}')
break
if ip.startswith('169.254.'):
time.sleep(1)
i += 1
else:
break
except:
time.sleep(1)
i += 1
@ -62,6 +68,21 @@ class Eth0Test(TestCase):
if i == timeout:
self.fail('Get ip timeout')
i = 0
while i < timeout:
try:
resolver = Resolver()
answer = resolver.query(site)
if answer:
print(f'nameserver: {answer.nameserver}')
break
except:
time.sleep(1)
i += 1
if i == timeout:
self.fail('DNS query timeout')
try:
result = subprocess.run(ping, capture_output=True, shell=True, timeout=timeout)
self.assertEqual(result.returncode, 0)

View file

@ -1,12 +1,13 @@
from unittest import TestCase
import os
import time
import socket
import struct
import fcntl
import subprocess
from dns.resolver import Resolver
class Eth1Test(TestCase):
LANGUAGES = {
'zh': {
@ -35,8 +36,9 @@ class Eth1Test(TestCase):
def test_ping(self):
ifname = 'eth1'
ping = f'ping -I {ifname} -c 3 baidu.com'
timeout = 10
site = 'baidu.com'
ping = f'ping -I {ifname} -c 3 {site}'
timeout = 15
i = 0
while i < timeout:
@ -54,7 +56,11 @@ class Eth1Test(TestCase):
try:
ip = self.get_ip(ifname)
print(f'{ifname}: {ip}')
break
if ip.startswith('169.254.'):
time.sleep(1)
i += 1
else:
break
except:
time.sleep(1)
i += 1
@ -62,6 +68,21 @@ class Eth1Test(TestCase):
if i == timeout:
self.fail('Get ip timeout')
i = 0
while i < timeout:
try:
resolver = Resolver()
answer = resolver.query(site)
if answer:
print(f'nameserver: {answer.nameserver}')
break
except:
time.sleep(1)
i += 1
if i == timeout:
self.fail('DNS query timeout')
try:
result = subprocess.run(ping, capture_output=True, shell=True, timeout=timeout)
self.assertEqual(result.returncode, 0)

View file

@ -15,9 +15,13 @@ class BTTest(TestCase):
}
def test_scan(self):
scan = 'hcitool -i hci0 scan'
# timeout: length * 1.28s
# devices: numrsp
scan = 'hcitool -i hci0 scan --length=5 --numrsp=1'
try:
result = subprocess.run(scan, capture_output=True, shell=True, timeout=10)
self.assertEqual(result.returncode, 0)
proc = subprocess.run(scan, capture_output=True, text=True, shell=True, timeout=10)
print(proc.stdout)
self.assertEqual(proc.returncode, 0)
self.assertGreater(len(proc.stdout.splitlines()), 1)
except subprocess.TimeoutExpired:
self.fail('Scan timeout')

View file

@ -0,0 +1,53 @@
from unittest import TestCase
import subprocess
import time
class WiFiTest(TestCase):
LANGUAGES = {
'zh': {
'WiFiTest': 'WiFi',
'test_scan': '扫描'
},
'en': {
'WiFiTest': 'WiFi',
'test_scan': 'Scan'
}
}
def _wpa_cli(self, command):
timeout = 10
cmd = f'wpa_cli -p/var/run/wpa_supplicant -iwlan0 {command}'
proc = subprocess.run(cmd, capture_output=True, text=True, shell=True, timeout=timeout)
print(f'Create wpa_cli subprocess run {command} return {proc.returncode}')
return proc.returncode, proc.stdout
def test_scan(self):
try:
timeout = 10
cmd = 'pidof wpa_supplicant'
proc = subprocess.run(cmd, capture_output=True, text=True, shell=True, timeout=timeout)
if proc.returncode:
cmd = 'wpa_supplicant -B -Dnl80211 -iwlan0 -c/etc/wpa_supplicant.conf'
proc = subprocess.run(cmd, capture_output=True, text=True, shell=True, timeout=timeout)
print(f'Create wpa_supplicant subprocess return {proc.returncode}')
self.assertEqual(proc.returncode, 0)
rc, out = self._wpa_cli('scan')
print(out)
self.assertEqual(rc, 0)
i = 0
while i < timeout:
rc, out = self._wpa_cli('scan_results')
print(out)
self.assertEqual(rc, 0)
if len(out.splitlines()) > 1:
break
time.sleep(1)
i += 1
if i == timeout:
self.fail('scan results without ap')
except subprocess.TimeoutExpired:
self.fail('Scan failed')

View file

@ -0,0 +1,32 @@
from unittest import TestCase
import os
import subprocess
#import usb.core
class USB4GModuleTest(TestCase):
LANGUAGES = {
'zh': {
'USB4GModuleTest': '4G模块',
'test_read_product': '读取型号'
},
'en': {
'USB4GModuleTest': '4G Moduel',
'test_read_product': 'Read product name'
}
}
def test_read_product(self):
product_file = '/sys/devices/platform/soc/c0980100.ehci1/usb1/1-1/product'
self.assertTrue(os.path.exists(product_file))
try:
timeout = 10
cmd = 'mmcli -m 0'
proc = subprocess.run(cmd, shell=True, capture_output=True,
text=True, timeout=timeout)
print(f'Create mmcli subprocess return {proc.returncode}')
self.assertEqual(proc.returncode, 0)
self.assertNotIn(proc.stdout, 'sim-missing')
except:
self.fail('Create mmcli subprocess failed')

View file

@ -1 +1 @@
MODULE_NAME = {'zh': '手动测试', 'en': 'Manual Test Item'}
MODULE_NAME = {'zh': '人工判断', 'en': 'Manual Test Item'}

View file

@ -0,0 +1,23 @@
from unittest import TestCase
import subprocess
class LCDTest(TestCase):
LANGUAGES = {
'zh': {
'LCDTest': 'MIPI屏',
'test_lcd': '彩色条纹'
},
'en': {
'LCDTest': 'MIPI Screen',
'test_lcd': 'Color Bar'
}
}
def test_lcd(self):
try:
cmd = 'modetest -M spacemit -s 136@127:1200x1920 -a -P 31@127:1200x1920+0+0@AR24'
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except:
self.fail('Create modetest subprocess fail')

View file

@ -0,0 +1,24 @@
from unittest import TestCase
import subprocess
class HDMIAudioTest(TestCase):
LANGUAGES = {
'zh': {
'HDMIAudioTest': 'HDMI Audio',
'test_hdmi_auido': '播放一段音乐'
},
'en': {
'HDMIAudioTest': 'HDMI Audio',
'test_hdmi_auido': 'Play a piece of music'
}
}
def test_hdmi_auido(self):
try:
music_file = '/opt/factorytest/res/canon.wav'
cmd = f'aplay -Dhw:0,0 -r 48000 -f S16_LE {music_file}'
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except:
self.fail('Create subprocess fail')

View file

@ -0,0 +1,25 @@
from unittest import TestCase
import subprocess
class FanTest(TestCase):
LANGUAGES = {
'zh': {
'FanTest': '风扇',
'test_speed': '开关'
},
'en': {
'FanTest': 'Fan',
'test_speed': 'On/Off',
'title': 'Fan'
}
}
def test_speed(self):
try:
gpio = 79
cmd = f'/opt/factorytest/utils/fan.sh {gpio}'
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except:
self.fail('Create fan.sh subprocess failed')

View file

@ -0,0 +1,25 @@
from unittest import TestCase
import subprocess
class GPIOTest(TestCase):
LANGUAGES = {
'zh': {
'GPIOTest': 'GPIO',
'test_gpio': '开灯'
},
'en': {
'GPIOTest': 'GPIO',
'test_gpio': 'Turn on light'
}
}
def test_gpio(self):
try:
gpios = '47 48 49 50 51 52 70 71 72 73 74 75 76 77 78 90 91 92'
cmd = f'/opt/factorytest/utils/gpio.sh {gpios}'
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except:
self.fail('Create gpio subprocess fail')

12
utils/fan.sh Executable file
View file

@ -0,0 +1,12 @@
#!/bin/bash -e
echo $1 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio$1/direction
while true
do
echo 1 > /sys/class/gpio/gpio$1/value
sleep 5
echo 0 > /sys/class/gpio/gpio$1/value
sleep 5
done

8
utils/gpio.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/bash -e
for gpio in "$@"
do
echo $gpio > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio$gpio/direction
echo 1 > /sys/class/gpio/gpio$gpio/value
done