hardpy 0.5.1__py3-none-any.whl → 0.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hardpy/__init__.py +10 -0
- hardpy/cli/__init__.py +0 -0
- hardpy/cli/cli.py +145 -0
- hardpy/cli/template.py +220 -0
- hardpy/common/__init__.py +0 -0
- hardpy/common/config.py +159 -0
- hardpy/hardpy_panel/api.py +49 -6
- hardpy/hardpy_panel/frontend/dist/asset-manifest.json +3 -3
- hardpy/hardpy_panel/frontend/dist/index.html +1 -1
- hardpy/hardpy_panel/frontend/dist/static/js/main.7c954faf.js +3 -0
- hardpy/hardpy_panel/frontend/dist/static/js/main.7c954faf.js.map +1 -0
- hardpy/pytest_hardpy/db/base_connector.py +7 -1
- hardpy/pytest_hardpy/db/base_server.py +4 -4
- hardpy/pytest_hardpy/db/base_store.py +13 -3
- hardpy/pytest_hardpy/db/const.py +3 -0
- hardpy/pytest_hardpy/db/schema.py +47 -11
- hardpy/pytest_hardpy/db/statestore.py +11 -0
- hardpy/pytest_hardpy/plugin.py +93 -33
- hardpy/pytest_hardpy/pytest_call.py +65 -5
- hardpy/pytest_hardpy/pytest_wrapper.py +62 -60
- hardpy/pytest_hardpy/reporter/base.py +1 -1
- hardpy/pytest_hardpy/reporter/hook_reporter.py +7 -3
- hardpy/pytest_hardpy/result/report_loader/couchdb_loader.py +3 -2
- hardpy/pytest_hardpy/result/report_reader/couchdb_reader.py +2 -2
- hardpy/pytest_hardpy/utils/__init__.py +7 -4
- hardpy/pytest_hardpy/utils/connection_data.py +17 -0
- hardpy/pytest_hardpy/utils/const.py +4 -14
- hardpy/pytest_hardpy/utils/dialog_box.py +1 -1
- hardpy/pytest_hardpy/utils/exception.py +14 -0
- hardpy/pytest_hardpy/utils/node_info.py +1 -1
- hardpy/pytest_hardpy/utils/progress_calculator.py +1 -1
- hardpy/pytest_hardpy/utils/singleton.py +1 -1
- {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/METADATA +25 -57
- {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/RECORD +38 -34
- {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/entry_points.txt +1 -1
- hardpy/hardpy_panel/frontend/dist/static/js/main.da686f40.js +0 -3
- hardpy/hardpy_panel/frontend/dist/static/js/main.da686f40.js.map +0 -1
- hardpy/hardpy_panel/runner.py +0 -54
- hardpy/pytest_hardpy/utils/config_data.py +0 -35
- /hardpy/hardpy_panel/frontend/dist/static/js/{main.da686f40.js.LICENSE.txt → main.7c954faf.js.LICENSE.txt} +0 -0
- {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/WHEEL +0 -0
- {hardpy-0.5.1.dist-info → hardpy-0.6.1.dist-info}/licenses/LICENSE +0 -0
hardpy/__init__.py
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
from hardpy.pytest_hardpy.result import CouchdbLoader
|
|
5
5
|
from hardpy.pytest_hardpy.utils import (
|
|
6
6
|
DuplicateSerialNumberError,
|
|
7
|
+
DuplicatePartNumberError,
|
|
8
|
+
DuplicateTestStandNameError,
|
|
7
9
|
DuplicateDialogBoxError,
|
|
8
10
|
)
|
|
9
11
|
from hardpy.pytest_hardpy.result.couchdb_config import CouchdbConfig
|
|
@@ -21,6 +23,8 @@ from hardpy.pytest_hardpy.pytest_call import (
|
|
|
21
23
|
get_current_report,
|
|
22
24
|
set_dut_info,
|
|
23
25
|
set_dut_serial_number,
|
|
26
|
+
set_dut_part_number,
|
|
27
|
+
set_stand_name,
|
|
24
28
|
set_stand_info,
|
|
25
29
|
set_case_artifact,
|
|
26
30
|
set_module_artifact,
|
|
@@ -28,6 +32,7 @@ from hardpy.pytest_hardpy.pytest_call import (
|
|
|
28
32
|
set_message,
|
|
29
33
|
set_driver_info,
|
|
30
34
|
run_dialog_box,
|
|
35
|
+
set_operator_message,
|
|
31
36
|
)
|
|
32
37
|
|
|
33
38
|
__all__ = [
|
|
@@ -37,16 +42,21 @@ __all__ = [
|
|
|
37
42
|
"get_current_report",
|
|
38
43
|
# Errors
|
|
39
44
|
"DuplicateSerialNumberError",
|
|
45
|
+
"DuplicatePartNumberError",
|
|
46
|
+
"DuplicateTestStandNameError",
|
|
40
47
|
"DuplicateDialogBoxError",
|
|
41
48
|
# Database info
|
|
42
49
|
"set_dut_info",
|
|
43
50
|
"set_dut_serial_number",
|
|
51
|
+
"set_dut_part_number",
|
|
52
|
+
"set_stand_name",
|
|
44
53
|
"set_stand_info",
|
|
45
54
|
"set_case_artifact",
|
|
46
55
|
"set_module_artifact",
|
|
47
56
|
"set_run_artifact",
|
|
48
57
|
"set_message",
|
|
49
58
|
"set_driver_info",
|
|
59
|
+
"set_operator_message",
|
|
50
60
|
# Dialog boxes
|
|
51
61
|
"run_dialog_box",
|
|
52
62
|
"DialogBox",
|
hardpy/cli/__init__.py
ADDED
|
File without changes
|
hardpy/cli/cli.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Copyright (c) 2024 Everypin
|
|
2
|
+
# GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from typing_extensions import Annotated
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from uvicorn import run as uvicorn_run
|
|
12
|
+
|
|
13
|
+
from hardpy.cli.template import TemplateGenerator
|
|
14
|
+
from hardpy.common.config import ConfigManager
|
|
15
|
+
|
|
16
|
+
cli = typer.Typer(add_completion=False)
|
|
17
|
+
default_config = ConfigManager().get_config()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@cli.command()
|
|
21
|
+
def init( # noqa: WPS211
|
|
22
|
+
tests_dir: Annotated[Optional[str], typer.Argument()] = None,
|
|
23
|
+
create_database: bool = typer.Option(
|
|
24
|
+
True,
|
|
25
|
+
help="Create CouchDB database.",
|
|
26
|
+
),
|
|
27
|
+
database_user: str = typer.Option(
|
|
28
|
+
default_config.database.user,
|
|
29
|
+
help="Specify a database user.",
|
|
30
|
+
),
|
|
31
|
+
database_password: str = typer.Option(
|
|
32
|
+
default_config.database.password,
|
|
33
|
+
help="Specify a database user password.",
|
|
34
|
+
),
|
|
35
|
+
database_host: str = typer.Option(
|
|
36
|
+
default_config.database.host,
|
|
37
|
+
help="Specify a database host.",
|
|
38
|
+
),
|
|
39
|
+
database_port: int = typer.Option(
|
|
40
|
+
default_config.database.port,
|
|
41
|
+
help="Specify a database port.",
|
|
42
|
+
),
|
|
43
|
+
frontend_host: str = typer.Option(
|
|
44
|
+
default_config.frontend.host,
|
|
45
|
+
help="Specify a frontend host.",
|
|
46
|
+
),
|
|
47
|
+
frontend_port: int = typer.Option(
|
|
48
|
+
default_config.frontend.port,
|
|
49
|
+
help="Specify a frontend port.",
|
|
50
|
+
),
|
|
51
|
+
socket_host: str = typer.Option(
|
|
52
|
+
default_config.socket.host,
|
|
53
|
+
help="Specify a socket host.",
|
|
54
|
+
),
|
|
55
|
+
socket_port: int = typer.Option(
|
|
56
|
+
default_config.socket.port,
|
|
57
|
+
help="Specify a socket port.",
|
|
58
|
+
),
|
|
59
|
+
):
|
|
60
|
+
"""Initialize HardPy tests directory.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
tests_dir (str | None): Tests directory. Current directory + `tests` by default
|
|
64
|
+
create_database (bool): Flag to create database
|
|
65
|
+
database_user (str): Database user name
|
|
66
|
+
database_password (str): Database password
|
|
67
|
+
database_host (str): Database host
|
|
68
|
+
database_port (int): Database port
|
|
69
|
+
frontend_host (str): Panel operator host
|
|
70
|
+
frontend_port (int): Panel operator port
|
|
71
|
+
socket_host (str): Socket host
|
|
72
|
+
socket_port (int): Socket port
|
|
73
|
+
"""
|
|
74
|
+
_tests_dir = tests_dir if tests_dir else default_config.tests_dir
|
|
75
|
+
ConfigManager().init_config(
|
|
76
|
+
tests_dir=str(_tests_dir),
|
|
77
|
+
database_user=database_user,
|
|
78
|
+
database_password=database_password,
|
|
79
|
+
database_host=database_host,
|
|
80
|
+
database_port=database_port,
|
|
81
|
+
frontend_host=frontend_host,
|
|
82
|
+
frontend_port=frontend_port,
|
|
83
|
+
socket_host=socket_host,
|
|
84
|
+
socket_port=socket_port,
|
|
85
|
+
)
|
|
86
|
+
# create tests directory
|
|
87
|
+
dir_path = Path(Path.cwd() / _tests_dir)
|
|
88
|
+
os.makedirs(dir_path, exist_ok=True)
|
|
89
|
+
|
|
90
|
+
if create_database:
|
|
91
|
+
# create database directory
|
|
92
|
+
os.makedirs(dir_path / "database", exist_ok=True)
|
|
93
|
+
|
|
94
|
+
# create hardpy.toml
|
|
95
|
+
ConfigManager().create_config(dir_path)
|
|
96
|
+
config = ConfigManager().read_config(dir_path)
|
|
97
|
+
if not config:
|
|
98
|
+
print(f"hardpy.toml config by path {dir_path} not detected.")
|
|
99
|
+
sys.exit()
|
|
100
|
+
|
|
101
|
+
template = TemplateGenerator(config)
|
|
102
|
+
|
|
103
|
+
files = {}
|
|
104
|
+
|
|
105
|
+
if create_database:
|
|
106
|
+
files[Path(dir_path / "docker-compose.yaml")] = template.docker_compose_yaml
|
|
107
|
+
files[Path(dir_path / "database" / "couchdb.ini")] = template.couchdb_ini
|
|
108
|
+
|
|
109
|
+
files[Path(dir_path / "pytest.ini")] = template.pytest_ini
|
|
110
|
+
files[Path(dir_path / "test_1.py")] = template.test_1_py
|
|
111
|
+
files[Path(dir_path / "conftest.py")] = template.conftest_py
|
|
112
|
+
|
|
113
|
+
for key, value in files.items():
|
|
114
|
+
template.create_file(key, value)
|
|
115
|
+
|
|
116
|
+
print(f"HardPy project {dir_path.name} initialized successfully.")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@cli.command()
|
|
120
|
+
def run(tests_dir: Annotated[Optional[str], typer.Argument()] = None):
|
|
121
|
+
"""Run HardPy server.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
tests_dir (Optional[str]): Test directory. Current directory by default
|
|
125
|
+
"""
|
|
126
|
+
dir_path = Path.cwd() / tests_dir if tests_dir else Path.cwd()
|
|
127
|
+
config = ConfigManager().read_config(dir_path)
|
|
128
|
+
|
|
129
|
+
if not config:
|
|
130
|
+
print(f"Config at path {dir_path} not found.")
|
|
131
|
+
sys.exit()
|
|
132
|
+
|
|
133
|
+
print("\nLaunch the HardPy operator panel...")
|
|
134
|
+
print(f"http://{config.frontend.host}:{config.frontend.port}\n")
|
|
135
|
+
|
|
136
|
+
uvicorn_run(
|
|
137
|
+
"hardpy.hardpy_panel.api:app",
|
|
138
|
+
host=config.frontend.host,
|
|
139
|
+
port=config.frontend.port,
|
|
140
|
+
log_level="critical",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
cli()
|
hardpy/cli/template.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Copyright (c) 2024 Everypin
|
|
2
|
+
# GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from hardpy.common.config import HardpyConfig
|
|
7
|
+
|
|
8
|
+
docker_compose_yaml = """version: "3.8"
|
|
9
|
+
|
|
10
|
+
services:
|
|
11
|
+
couchserver:
|
|
12
|
+
image: couchdb:3.3.2
|
|
13
|
+
ports:
|
|
14
|
+
- "{}:5984"
|
|
15
|
+
environment:
|
|
16
|
+
COUCHDB_USER: {}
|
|
17
|
+
COUCHDB_PASSWORD: {}
|
|
18
|
+
volumes:
|
|
19
|
+
- ./database/dbdata:/opt/couchdb/data
|
|
20
|
+
- ./database/couchdb.ini:/opt/couchdb/etc/local.ini
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
couchdb_ini = """; CouchDB Configuration Settings
|
|
24
|
+
|
|
25
|
+
; Custom settings should be made in this file. They will override settings
|
|
26
|
+
; in default.ini, but unlike changes made to default.ini, this file won't be
|
|
27
|
+
; overwritten on server upgrade.
|
|
28
|
+
|
|
29
|
+
[couchdb]
|
|
30
|
+
;max_document_size = 4294967296 ; bytes
|
|
31
|
+
;os_process_timeout = 5000
|
|
32
|
+
|
|
33
|
+
[couch_peruser]
|
|
34
|
+
; If enabled, couch_peruser ensures that a private per-user database
|
|
35
|
+
; exists for each document in _users. These databases are writable only
|
|
36
|
+
; by the corresponding user. Databases are in the following form:
|
|
37
|
+
; userdb-{{hex encoded username}}
|
|
38
|
+
;enable = true
|
|
39
|
+
|
|
40
|
+
; If set to true and a user is deleted, the respective database gets
|
|
41
|
+
; deleted as well.
|
|
42
|
+
;delete_dbs = true
|
|
43
|
+
|
|
44
|
+
; Set a default q value for peruser-created databases that is different from
|
|
45
|
+
; cluster / q
|
|
46
|
+
;q = 1
|
|
47
|
+
|
|
48
|
+
[chttpd]
|
|
49
|
+
;port = {}
|
|
50
|
+
;bind_address = {}
|
|
51
|
+
|
|
52
|
+
; Options for the MochiWeb HTTP server.
|
|
53
|
+
;server_options = [{{backlog, 128}}, {{acceptor_pool_size, 16}}]
|
|
54
|
+
|
|
55
|
+
; For more socket options, consult Erlang's module 'inet' man page.
|
|
56
|
+
;socket_options = [{{sndbuf, 262144}}, {{nodelay, true}}]
|
|
57
|
+
|
|
58
|
+
enable_cors=true
|
|
59
|
+
|
|
60
|
+
[httpd]
|
|
61
|
+
; NOTE that this only configures the "backend" node-local port, not the
|
|
62
|
+
; "frontend" clustered port. You probably don't want to change anything in
|
|
63
|
+
; this section.
|
|
64
|
+
; Uncomment next line to trigger basic-auth popup on unauthorized requests.
|
|
65
|
+
;WWW-Authenticate = Basic realm="administrator"
|
|
66
|
+
|
|
67
|
+
; Uncomment next line to set the configuration modification whitelist. Only
|
|
68
|
+
; whitelisted values may be changed via the /_config URLs. To allow the admin
|
|
69
|
+
; to change this value over HTTP, remember to include {{httpd,config_whitelist}}
|
|
70
|
+
; itself. Excluding it from the list would require editing this file to update
|
|
71
|
+
; the whitelist.
|
|
72
|
+
;config_whitelist = [{{httpd,config_whitelist}}, {{log,level}}, {{etc,etc}}]
|
|
73
|
+
|
|
74
|
+
[cors]
|
|
75
|
+
origins = *
|
|
76
|
+
methods = GET, PUT, POST, HEAD, DELETE
|
|
77
|
+
credentials = true
|
|
78
|
+
headers = accept, authorization, content-type, origin, referer, x-csrf-token
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
[ssl]
|
|
82
|
+
;enable = true
|
|
83
|
+
;cert_file = /full/path/to/server_cert.pem
|
|
84
|
+
;key_file = /full/path/to/server_key.pem
|
|
85
|
+
;password = somepassword
|
|
86
|
+
|
|
87
|
+
; set to true to validate peer certificates
|
|
88
|
+
;verify_ssl_certificates = false
|
|
89
|
+
|
|
90
|
+
; Set to true to fail if the client does not send a certificate. Only used if verify_ssl_certificates is true.
|
|
91
|
+
;fail_if_no_peer_cert = false
|
|
92
|
+
|
|
93
|
+
; Path to file containing PEM encoded CA certificates (trusted
|
|
94
|
+
; certificates used for verifying a peer certificate). May be omitted if
|
|
95
|
+
; you do not want to verify the peer.
|
|
96
|
+
;cacert_file = /full/path/to/cacertf
|
|
97
|
+
|
|
98
|
+
; The verification fun (optional) if not specified, the default
|
|
99
|
+
; verification fun will be used.
|
|
100
|
+
;verify_fun = {{Module, VerifyFun}}
|
|
101
|
+
|
|
102
|
+
; maximum peer certificate depth
|
|
103
|
+
;ssl_certificate_max_depth = 1
|
|
104
|
+
|
|
105
|
+
; Reject renegotiations that do not live up to RFC 5746.
|
|
106
|
+
;secure_renegotiate = true
|
|
107
|
+
|
|
108
|
+
; The cipher suites that should be supported.
|
|
109
|
+
; Can be specified in erlang format "{{ecdhe_ecdsa,aes_128_cbc,sha256}}"
|
|
110
|
+
; or in OpenSSL format "ECDHE-ECDSA-AES128-SHA256".
|
|
111
|
+
;ciphers = ["ECDHE-ECDSA-AES128-SHA256", "ECDHE-ECDSA-AES128-SHA"]
|
|
112
|
+
|
|
113
|
+
; The SSL/TLS versions to support
|
|
114
|
+
;tls_versions = [tlsv1, 'tlsv1.1', 'tlsv1.2']
|
|
115
|
+
|
|
116
|
+
; To enable Virtual Hosts in CouchDB, add a vhost = path directive. All requests to
|
|
117
|
+
; the Virtual Host will be redirected to the path. In the example below all requests
|
|
118
|
+
; to http://example.com/ are redirected to /database.
|
|
119
|
+
; If you run CouchDB on a specific port, include the port number in the vhost:
|
|
120
|
+
; example.com:5984 = /database
|
|
121
|
+
[vhosts]
|
|
122
|
+
;example.com = /database/
|
|
123
|
+
|
|
124
|
+
; To create an admin account uncomment the '[admins]' section below and add a
|
|
125
|
+
; line in the format 'username = password'. When you next start CouchDB, it
|
|
126
|
+
; will change the password to a hash (so that your passwords don't linger
|
|
127
|
+
; around in plain-text files). You can add more admin accounts with more
|
|
128
|
+
; 'username = password' lines. Don't forget to restart CouchDB after
|
|
129
|
+
; changing this.
|
|
130
|
+
[admins]
|
|
131
|
+
;admin = mysecretpassword
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
pytest_ini = """[pytest]
|
|
135
|
+
log_cli = true
|
|
136
|
+
log_cli_level = INFO
|
|
137
|
+
log_cli_format = %%(asctime)s [%%(levelname)s] %%(message)s
|
|
138
|
+
log_cli_date_format = %H:%M:%S
|
|
139
|
+
addopts = --hardpy-pt
|
|
140
|
+
--hardpy-db-url http://{}:{}@{}:{}/
|
|
141
|
+
--hardpy-sh {}
|
|
142
|
+
--hardpy-sp {}
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
test_1_py = """import pytest
|
|
146
|
+
import hardpy
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
pytestmark = pytest.mark.module_name("HardPy template")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@pytest.mark.case_name("Test 1")
|
|
153
|
+
def test_one():
|
|
154
|
+
assert True
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
conftest_py = """import pytest
|
|
158
|
+
import hardpy
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def finish_executing():
|
|
162
|
+
print("Testing completed")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
166
|
+
def fill_actions_after_test(post_run_functions: list):
|
|
167
|
+
post_run_functions.append(finish_executing)
|
|
168
|
+
yield
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class TemplateGenerator:
|
|
173
|
+
"""HardPy template files generator."""
|
|
174
|
+
|
|
175
|
+
def __init__(self, config: HardpyConfig):
|
|
176
|
+
self._config = config
|
|
177
|
+
|
|
178
|
+
def create_file(self, file_path: Path, content: str):
|
|
179
|
+
"""Create HardPy template file.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
file_path (Path): file path
|
|
183
|
+
content (str): file content
|
|
184
|
+
"""
|
|
185
|
+
with open(file_path, "w") as file:
|
|
186
|
+
file.write(content)
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def docker_compose_yaml(self) -> str:
|
|
190
|
+
return docker_compose_yaml.format(
|
|
191
|
+
self._config.database.port,
|
|
192
|
+
self._config.database.user,
|
|
193
|
+
self._config.database.password,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def couchdb_ini(self) -> str:
|
|
198
|
+
return couchdb_ini.format(
|
|
199
|
+
self._config.database.port,
|
|
200
|
+
self._config.database.host,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def pytest_ini(self) -> str:
|
|
205
|
+
return pytest_ini.format(
|
|
206
|
+
self._config.database.user,
|
|
207
|
+
self._config.database.password,
|
|
208
|
+
self._config.database.host,
|
|
209
|
+
self._config.database.port,
|
|
210
|
+
self._config.socket.host,
|
|
211
|
+
self._config.socket.port,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def test_1_py(self) -> str: # noqa: WPS114
|
|
216
|
+
return test_1_py
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def conftest_py(self) -> str:
|
|
220
|
+
return conftest_py
|
|
File without changes
|
hardpy/common/config.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Copyright (c) 2024 Everypin
|
|
2
|
+
# GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
|
+
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import rtoml
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, ValidationError
|
|
9
|
+
|
|
10
|
+
logger = getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DatabaseConfig(BaseModel):
|
|
14
|
+
"""Database configuration."""
|
|
15
|
+
|
|
16
|
+
model_config = ConfigDict(extra="forbid")
|
|
17
|
+
|
|
18
|
+
user: str = "dev"
|
|
19
|
+
password: str = "dev"
|
|
20
|
+
host: str = "localhost"
|
|
21
|
+
port: int = 5984
|
|
22
|
+
|
|
23
|
+
def connection_url(self) -> str:
|
|
24
|
+
"""Get database connection url.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
str: database connection url
|
|
28
|
+
"""
|
|
29
|
+
credentials = f"{self.user}:{self.password}"
|
|
30
|
+
uri = f"{self.host}:{str(self.port)}" # noqa: WPS237
|
|
31
|
+
return f"http://{credentials}@{uri}/"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FrontendConfig(BaseModel):
|
|
35
|
+
"""Frontend configuration."""
|
|
36
|
+
|
|
37
|
+
model_config = ConfigDict(extra="forbid")
|
|
38
|
+
|
|
39
|
+
host: str = "localhost"
|
|
40
|
+
port: int = 8000
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SocketConfig(BaseModel):
|
|
44
|
+
"""Socket configuration."""
|
|
45
|
+
|
|
46
|
+
model_config = ConfigDict(extra="forbid")
|
|
47
|
+
|
|
48
|
+
host: str = "localhost"
|
|
49
|
+
port: int = 6525
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class HardpyConfig(BaseModel):
|
|
53
|
+
"""HardPy configuration."""
|
|
54
|
+
|
|
55
|
+
model_config = ConfigDict(extra="forbid")
|
|
56
|
+
|
|
57
|
+
title: str = "HardPy TOML config"
|
|
58
|
+
tests_dir: str = "tests"
|
|
59
|
+
database: DatabaseConfig = DatabaseConfig()
|
|
60
|
+
frontend: FrontendConfig = FrontendConfig()
|
|
61
|
+
socket: SocketConfig = SocketConfig()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ConfigManager:
|
|
65
|
+
"""HardPy configuration manager."""
|
|
66
|
+
|
|
67
|
+
obj = HardpyConfig()
|
|
68
|
+
tests_path = Path.cwd()
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def init_config( # noqa: WPS211
|
|
72
|
+
cls,
|
|
73
|
+
tests_dir: str,
|
|
74
|
+
database_user: str,
|
|
75
|
+
database_password: str,
|
|
76
|
+
database_host: str,
|
|
77
|
+
database_port: int,
|
|
78
|
+
frontend_host: str,
|
|
79
|
+
frontend_port: int,
|
|
80
|
+
socket_host: str,
|
|
81
|
+
socket_port: int,
|
|
82
|
+
):
|
|
83
|
+
"""Initialize HardPy configuration.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
tests_dir (str): Tests directory.
|
|
87
|
+
database_user (str): Database user name.
|
|
88
|
+
database_password (str): Database password.
|
|
89
|
+
database_host (str): Database host.
|
|
90
|
+
database_port (int): Database port.
|
|
91
|
+
frontend_host (str): Operator panel host.
|
|
92
|
+
frontend_port (int): Operator panel port.
|
|
93
|
+
socket_host (str): Socket host.
|
|
94
|
+
socket_port (int): Socket port.
|
|
95
|
+
"""
|
|
96
|
+
cls.obj.tests_dir = str(tests_dir)
|
|
97
|
+
cls.obj.database.user = database_user
|
|
98
|
+
cls.obj.database.password = database_password
|
|
99
|
+
cls.obj.database.host = database_host
|
|
100
|
+
cls.obj.database.port = database_port
|
|
101
|
+
cls.obj.frontend.host = frontend_host
|
|
102
|
+
cls.obj.frontend.port = frontend_port
|
|
103
|
+
cls.obj.socket.host = socket_host
|
|
104
|
+
cls.obj.socket.port = socket_port
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def create_config(cls, parent_dir: Path):
|
|
108
|
+
"""Create HardPy configuration.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
parent_dir (Path): Configuration file parent directory.
|
|
112
|
+
"""
|
|
113
|
+
with open(parent_dir / "hardpy.toml", "w") as file:
|
|
114
|
+
file.write(rtoml.dumps(cls.obj.model_dump()))
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def read_config(cls, toml_path: Path) -> HardpyConfig | None:
|
|
118
|
+
"""Read HardPy configuration.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
toml_path (Path): hardpy.toml file path.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
HardpyConfig | None: HardPy configuration
|
|
125
|
+
"""
|
|
126
|
+
cls.tests_path = toml_path
|
|
127
|
+
toml_file = toml_path / "hardpy.toml"
|
|
128
|
+
if not toml_file.exists():
|
|
129
|
+
logger.error(f"File hardpy.toml not found at path: {toml_file}")
|
|
130
|
+
return None
|
|
131
|
+
try:
|
|
132
|
+
with open(toml_path / "hardpy.toml", "r") as f:
|
|
133
|
+
cls.obj = HardpyConfig(**rtoml.load(f))
|
|
134
|
+
return cls.obj
|
|
135
|
+
except rtoml.TomlParsingError as exc:
|
|
136
|
+
logger.error(f"Error parsing TOML: {exc}")
|
|
137
|
+
except rtoml.TomlSerializationError as exc:
|
|
138
|
+
logger.error(f"Error parsing TOML: {exc}")
|
|
139
|
+
except ValidationError as exc:
|
|
140
|
+
logger.error(f"Error parsing TOML: {exc}")
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def get_config(cls) -> HardpyConfig:
|
|
145
|
+
"""Get HardPy configuration.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
HardpyConfig: HardPy configuration
|
|
149
|
+
"""
|
|
150
|
+
return cls.obj
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def get_tests_path(cls) -> Path: # noqa: WPS615
|
|
154
|
+
"""Get tests path.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Path: HardPy tests path
|
|
158
|
+
"""
|
|
159
|
+
return cls.tests_path
|
hardpy/hardpy_panel/api.py
CHANGED
|
@@ -2,17 +2,34 @@
|
|
|
2
2
|
# GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from urllib.parse import unquote
|
|
5
8
|
|
|
6
9
|
from fastapi import FastAPI
|
|
7
10
|
from fastapi.staticfiles import StaticFiles
|
|
8
11
|
|
|
9
|
-
from hardpy.
|
|
12
|
+
from hardpy.common.config import ConfigManager
|
|
10
13
|
from hardpy.pytest_hardpy.pytest_wrapper import PyTestWrapper
|
|
11
14
|
|
|
12
15
|
app = FastAPI()
|
|
13
16
|
app.state.pytest_wrp = PyTestWrapper()
|
|
14
17
|
|
|
15
18
|
|
|
19
|
+
class Status(str, Enum): # noqa: WPS600
|
|
20
|
+
"""Pytest run status.
|
|
21
|
+
|
|
22
|
+
Statuses, that can be returned by HardPy to frontend.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
STOPPED = "stopped"
|
|
26
|
+
STARTED = "started"
|
|
27
|
+
COLLECTED = "collected"
|
|
28
|
+
BUSY = "busy"
|
|
29
|
+
READY = "ready"
|
|
30
|
+
ERROR = "error"
|
|
31
|
+
|
|
32
|
+
|
|
16
33
|
@app.get("/api/start")
|
|
17
34
|
def start_pytest():
|
|
18
35
|
"""Start pytest subprocess.
|
|
@@ -55,12 +72,12 @@ def couch_connection():
|
|
|
55
72
|
"""Get couchdb connection string.
|
|
56
73
|
|
|
57
74
|
Returns:
|
|
58
|
-
dict[str, str
|
|
75
|
+
dict[str, str]: couchdb connection string
|
|
59
76
|
"""
|
|
60
|
-
|
|
77
|
+
connection_url = ConfigManager().get_config().database.connection_url()
|
|
61
78
|
|
|
62
79
|
return {
|
|
63
|
-
"connection_str":
|
|
80
|
+
"connection_str": connection_url,
|
|
64
81
|
}
|
|
65
82
|
|
|
66
83
|
|
|
@@ -74,13 +91,39 @@ def confirm_dialog_box(dialog_box_output: str):
|
|
|
74
91
|
Returns:
|
|
75
92
|
dict[str, RunStatus]: run status
|
|
76
93
|
"""
|
|
77
|
-
|
|
94
|
+
hex_base = 16
|
|
95
|
+
unquoted_string = unquote(dialog_box_output)
|
|
96
|
+
decoded_string = re.sub(
|
|
97
|
+
"%([0-9a-fA-F]{2})",
|
|
98
|
+
lambda match: chr(int(match.group(1), hex_base)),
|
|
99
|
+
unquoted_string,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if app.state.pytest_wrp.send_data(str(decoded_string)):
|
|
103
|
+
return {"status": Status.BUSY}
|
|
104
|
+
return {"status": Status.ERROR}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@app.post("/api/confirm_operator_msg/{is_msg_visible}")
|
|
108
|
+
def confirm_operator_msg(is_msg_visible: bool):
|
|
109
|
+
"""Confirm operator msg.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
is_msg_visible (bool): is operator message is visible
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
dict[str, RunStatus]: run status
|
|
116
|
+
"""
|
|
117
|
+
if app.state.pytest_wrp.send_data(str(is_msg_visible)):
|
|
78
118
|
return {"status": Status.BUSY}
|
|
79
119
|
return {"status": Status.ERROR}
|
|
80
120
|
|
|
81
121
|
|
|
82
122
|
app.mount(
|
|
83
123
|
"/",
|
|
84
|
-
StaticFiles(
|
|
124
|
+
StaticFiles(
|
|
125
|
+
directory=(os.path.dirname(__file__)) + "/frontend/dist", # noqa: WPS336
|
|
126
|
+
html=True,
|
|
127
|
+
),
|
|
85
128
|
name="static",
|
|
86
129
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"files": {
|
|
3
3
|
"main.css": "/static/css/main.e8a862f1.css",
|
|
4
|
-
"main.js": "/static/js/main.
|
|
4
|
+
"main.js": "/static/js/main.7c954faf.js",
|
|
5
5
|
"blueprint-icons-all-paths-loader.js": "/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js",
|
|
6
6
|
"blueprint-icons-split-paths-by-size-loader.js": "/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js",
|
|
7
7
|
"static/js/808.ce070002.chunk.js": "/static/js/808.ce070002.chunk.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"static/media/logo_smol.png": "/static/media/logo_smol.5b16f92447a4a9e80331.png",
|
|
22
22
|
"index.html": "/index.html",
|
|
23
23
|
"main.e8a862f1.css.map": "/static/css/main.e8a862f1.css.map",
|
|
24
|
-
"main.
|
|
24
|
+
"main.7c954faf.js.map": "/static/js/main.7c954faf.js.map",
|
|
25
25
|
"blueprint-icons-all-paths-loader.0aa89747.chunk.js.map": "/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js.map",
|
|
26
26
|
"blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js.map": "/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js.map",
|
|
27
27
|
"808.ce070002.chunk.js.map": "/static/js/808.ce070002.chunk.js.map",
|
|
@@ -31,6 +31,6 @@
|
|
|
31
31
|
},
|
|
32
32
|
"entrypoints": [
|
|
33
33
|
"static/css/main.e8a862f1.css",
|
|
34
|
-
"static/js/main.
|
|
34
|
+
"static/js/main.7c954faf.js"
|
|
35
35
|
]
|
|
36
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>HardPy Operator Panel</title><script defer="defer" src="/static/js/main.
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>HardPy Operator Panel</title><script defer="defer" src="/static/js/main.7c954faf.js"></script><link href="/static/css/main.e8a862f1.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|