Mercurial > libervia-backend
comparison tests/e2e/run_e2e.py @ 3429:d4558f3cbf13
tests, docker(e2e): added e2e tests for Libervia:
- moved jp tests to `e2e/jp`
- new fixtures
- adapted docker-compose
- improved `run_e2e` with several flags + report on failure
- doc to come
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 27 Nov 2020 16:39:40 +0100 |
parents | 814e118d9ef3 |
children | be6d91572633 |
comparison
equal
deleted
inserted
replaced
3428:a6ea53248c14 | 3429:d4558f3cbf13 |
---|---|
20 | 20 |
21 import sys | 21 import sys |
22 from pathlib import Path | 22 from pathlib import Path |
23 import tempfile | 23 import tempfile |
24 from textwrap import dedent | 24 from textwrap import dedent |
25 from datetime import datetime | |
25 import sh | 26 import sh |
27 import io | |
28 import re | |
29 import sat_templates | |
30 import libervia | |
26 from sat.core import exceptions | 31 from sat.core import exceptions |
27 | 32 import yaml |
28 KEEP_OPT = "--keep" | 33 try: |
34 from yaml import CLoader as Loader, CDumper as Dumper | |
35 except ImportError: | |
36 from yaml import Loader, Dumper | |
37 | |
38 | |
39 OPT_KEEP_CONTAINERS = "--keep-containers" | |
40 OPT_KEEP_PROFILES = "--keep-profiles" | |
41 OPT_KEEP_VNC = "--keep-vnc" | |
42 OPT_KEEP_BROWSER = "--keep-browser" | |
43 OPT_VISUAL = "--visual" | |
44 OPT_DEV_MODE = "--dev-mode" | |
45 | |
46 dev_mode_inst = dedent("""\ | |
47 Here is a short script to start working with a logged account: | |
48 | |
49 from helium import * | |
50 start_firefox() | |
51 go_to("https://libervia.test:8443/login") | |
52 write("account1", "login") | |
53 write("test", "password") | |
54 click("log in") | |
55 """) | |
56 report_buffer = io.StringIO() | |
29 | 57 |
30 | 58 |
31 def live_out(data): | 59 def live_out(data): |
32 sys.stdout.write(data) | 60 sys.stdout.write(data) |
33 sys.stdout.flush() | 61 sys.stdout.flush() |
62 report_buffer.write(data) | |
34 | 63 |
35 | 64 |
36 def live_err(data): | 65 def live_err(data): |
37 sys.stderr.write(data) | 66 sys.stderr.write(data) |
38 sys.stderr.flush() | 67 sys.stderr.flush() |
68 report_buffer.write(data) | |
69 | |
70 | |
71 def get_opt(opt_name): | |
72 """Check is an option flag is set, and remove it for sys.argv | |
73 | |
74 This allow to have simple flags without interfering with pytest options | |
75 """ | |
76 if opt_name in sys.argv: | |
77 sys.argv.remove(opt_name) | |
78 return True | |
79 else: | |
80 return False | |
81 | |
82 | |
83 def set_env(override, name, value="1"): | |
84 """Set environement variable""" | |
85 environment = override["services"]["sat"].setdefault("environment", {}) | |
86 environment[name] = value | |
39 | 87 |
40 | 88 |
41 def use_e2e_env(): | 89 def use_e2e_env(): |
42 if KEEP_OPT in sys.argv: | 90 visual = get_opt(OPT_VISUAL) |
91 keep_containers = get_opt(OPT_KEEP_CONTAINERS) | |
92 keep_profiles = get_opt(OPT_KEEP_PROFILES) | |
93 keep_vnc = get_opt(OPT_KEEP_VNC) | |
94 keep_browser = get_opt(OPT_KEEP_BROWSER) | |
95 if keep_browser: | |
43 keep_containers = True | 96 keep_containers = True |
44 sys.argv.remove(KEEP_OPT) | 97 keep_vnc = True |
45 else: | 98 if keep_vnc: |
46 keep_containers = False | 99 visual = True |
100 dev_mode = get_opt(OPT_DEV_MODE) | |
101 if dev_mode: | |
102 keep_containers = keep_profiles = keep_vnc = visual = True | |
103 | |
47 for p in Path.cwd().parents: | 104 for p in Path.cwd().parents: |
48 package_path = p / "sat" | 105 package_path = p / "sat" |
49 docker_path = p / "docker" | 106 docker_path = p / "docker" |
50 if package_path.is_dir() and docker_path.is_dir(): | 107 if package_path.is_dir() and docker_path.is_dir(): |
51 sat_root_path = p | 108 sat_root_path = p |
54 raise exceptions.NotFound( | 111 raise exceptions.NotFound( |
55 "Can't find root of SàT code, are you sure that you are running the test " | 112 "Can't find root of SàT code, are you sure that you are running the test " |
56 "from the backend repository?" | 113 "from the backend repository?" |
57 ) | 114 ) |
58 | 115 |
116 libervia_path = Path(libervia.__file__).parent.resolve() | |
117 libervia_root_path = libervia_path.parent | |
118 if (libervia_root_path / ".hg").is_dir(): | |
119 libervia_source = libervia_root_path | |
120 libervia_target = "/src/libervia" | |
121 else: | |
122 libervia_source = libervia_path | |
123 libervia_target = "/src/libervia/libervia" | |
124 | |
125 sat_templates_path = Path(sat_templates.__file__).parent.resolve() | |
126 sat_templates_root_path = sat_templates_path.parent | |
127 if (sat_templates_root_path / ".hg").is_dir(): | |
128 sat_templates_source = sat_templates_root_path | |
129 sat_templates_target = "/src/sat_templates" | |
130 else: | |
131 sat_templates_source = sat_templates_path | |
132 sat_templates_target = "/src/sat_templates/sat_templates" | |
133 | |
59 compose_e2e_path = docker_path / "docker-compose_e2e.yml" | 134 compose_e2e_path = docker_path / "docker-compose_e2e.yml" |
60 if not compose_e2e_path.is_file(): | 135 if not compose_e2e_path.is_file(): |
61 raise exceptions.NotFound('"docker-compose_e2e.yml" file can\'t be found') | 136 raise exceptions.NotFound('"docker-compose_e2e.yml" file can\'t be found') |
62 | 137 |
63 with tempfile.TemporaryDirectory() as temp_dir: | 138 with tempfile.TemporaryDirectory(prefix="sat_test_e2e_") as temp_dir: |
64 override_path = Path(temp_dir) / "test_override.yml" | 139 override_path = Path(temp_dir) / "test_override.yml" |
65 with override_path.open("w") as f: | 140 override = yaml.load( |
66 f.write(dedent(f"""\ | 141 dedent(f"""\ |
67 version: "3.6" | 142 version: "3.6" |
68 services: | 143 services: |
69 sat: | 144 sat: |
70 volumes: | 145 volumes: |
71 - type: bind | 146 - type: bind |
72 source: {sat_root_path} | 147 source: {sat_root_path} |
73 target: /src/sat | 148 target: /src/sat |
74 read_only: true | 149 read_only: true |
75 """)) | 150 libervia: |
151 volumes: | |
152 - type: bind | |
153 source: {sat_root_path} | |
154 target: /src/sat | |
155 read_only: true | |
156 - type: bind | |
157 source: {libervia_source} | |
158 target: {libervia_target} | |
159 read_only: true | |
160 - type: bind | |
161 source: {sat_templates_source} | |
162 target: {sat_templates_target} | |
163 read_only: true | |
164 """ | |
165 ), | |
166 Loader=Loader | |
167 ) | |
168 | |
169 if keep_profiles: | |
170 set_env(override, "SAT_TEST_E2E_KEEP_PROFILES") | |
171 | |
172 if visual: | |
173 set_env(override, "SAT_TEST_E2E_LIBERVIA_NO_HEADLESS") | |
174 | |
175 if keep_browser: | |
176 set_env(override, "SAT_TEST_E2E_LIBERVIA_KEEP_BROWSER") | |
177 | |
178 with override_path.open("w") as f: | |
179 yaml.dump(override, f, Dumper=Dumper) | |
76 | 180 |
77 docker_compose = sh.docker_compose.bake( | 181 docker_compose = sh.docker_compose.bake( |
78 "-f", compose_e2e_path, "-f", override_path) | 182 "-f", compose_e2e_path, "-f", override_path) |
79 docker_compose.up("-d") | 183 docker_compose.up("-d") |
80 | 184 |
81 try: | 185 p = docker_compose.exec( |
82 docker_compose.exec( | |
83 "-T", "--workdir", "/src/sat/tests", "sat", | 186 "-T", "--workdir", "/src/sat/tests", "sat", |
84 "pytest", "-o", "cache_dir=/tmp", *sys.argv[1:], color="yes", | 187 "pytest", "-o", "cache_dir=/tmp", *sys.argv[1:], color="yes", |
85 _in=sys.stdin, _out=live_out, _out_bufsize=0, _err=live_err, _err_bufsize=0 | 188 _in=sys.stdin, _out=live_out, _out_bufsize=0, _err=live_err, _err_bufsize=0, |
189 _bg=True | |
86 ) | 190 ) |
191 if visual: | |
192 vnc_port = docker_compose.port("sat", "5900").split(':', 1)[1].strip() | |
193 p_vnc = sh.vncviewer( | |
194 f"localhost:{vnc_port}", | |
195 _bg=True, | |
196 # vncviewer exits with 1 when we send an SIGTERM to it, and it's printed | |
197 # before we can catch it (it's happening in a thread). Thus we set exit | |
198 # code 1 as OK to avoid the backtrace. | |
199 _ok_code=[0, 1] | |
200 ) | |
201 else: | |
202 p_vnc = None | |
203 | |
204 try: | |
205 p.wait() | |
87 except sh.ErrorReturnCode as e: | 206 except sh.ErrorReturnCode as e: |
207 sat_cont_id = docker_compose.ps("-q", "sat").strip() | |
208 report_dest = Path(f"reports_{datetime.now().isoformat()}/") | |
209 # we need to make `report_dest` explicitely local with "./", otherwise | |
210 # docker parse takes it as a container path due to the presence of ":" | |
211 # with `isoformat()`. | |
212 sh.docker.cp(f"{sat_cont_id}:/reports", f"./{report_dest}") | |
213 # we save 2 versions: one with ANSI escape codes | |
214 report_ansi = report_dest / "report.ansi" | |
215 with report_ansi.open('w') as f: | |
216 f.write(report_buffer.getvalue()) | |
217 # and one without (cf. https://stackoverflow.com/a/14693789) | |
218 ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') | |
219 report_log = report_dest / "report.log" | |
220 with report_log.open('w') as f: | |
221 f.write(ansi_escape.sub('', report_buffer.getvalue())) | |
222 | |
223 print(f"report saved to {report_dest}") | |
88 sys.exit(e.exit_code) | 224 sys.exit(e.exit_code) |
89 finally: | 225 finally: |
226 if p_vnc is not None and p_vnc.is_alive() and not keep_vnc: | |
227 p_vnc.terminate() | |
90 if not keep_containers: | 228 if not keep_containers: |
91 docker_compose.down(volumes=True) | 229 docker_compose.down(volumes=True) |
230 if dev_mode: | |
231 print(dev_mode_inst) | |
232 | |
92 | 233 |
93 if __name__ == "__main__": | 234 if __name__ == "__main__": |
94 use_e2e_env() | 235 use_e2e_env() |