teuton 3.3.0 → 3.4.0

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -14
  3. data/docs/commands/README.md +5 -4
  4. data/docs/commands/{readme.md → doc.md} +3 -5
  5. data/docs/commands/help.md +8 -8
  6. data/docs/devel/todo.md +1 -4
  7. data/docs/dsl/README.md +9 -8
  8. data/docs/dsl/event.md +172 -0
  9. data/docs/learn/01-cmd_new.md +1 -1
  10. data/docs/learn/02-target.md +18 -18
  11. data/docs/learn/03-remote_hosts.md +1 -1
  12. data/docs/learn/04-config.md +15 -13
  13. data/docs/learn/05-use.md +1 -1
  14. data/docs/learn/06-cmd_check.md +2 -2
  15. data/docs/learn/07-target_weight.md +4 -2
  16. data/docs/learn/08-unique_values.md +1 -1
  17. data/docs/learn/09-send.md +2 -2
  18. data/docs/learn/10-debug.md +10 -9
  19. data/docs/learn/11-export.md +1 -1
  20. data/docs/learn/12-preserve.md +4 -4
  21. data/docs/learn/13-feedback.md +3 -3
  22. data/docs/learn/14-moodle_id.md +3 -3
  23. data/docs/learn/15-readme.md +10 -10
  24. data/docs/learn/16-include.md +3 -3
  25. data/docs/learn/17-alias.md +2 -2
  26. data/docs/learn/18-log.md +3 -2
  27. data/docs/learn/19-read_vars.md +1 -1
  28. data/docs/learn/20-macros.md +1 -1
  29. data/docs/learn/21-exit_codes.md +1 -1
  30. data/docs/learn/22-result.md +1 -1
  31. data/docs/learn/23-test-code.md +1 -1
  32. data/docs/learn/24-test-sql.md +1 -1
  33. data/docs/learn/25-expect-result.md +1 -1
  34. data/docs/learn/26-expect_sequence.md +1 -1
  35. data/docs/learn/27-run_script.md +1 -1
  36. data/docs/learn/28-upload.md +3 -1
  37. data/docs/learn/31-event-stop.md +134 -0
  38. data/docs/learn/32-event-zero.md +127 -0
  39. data/docs/more.md +4 -1
  40. data/lib/teuton/case/action/manager.rb +146 -11
  41. data/lib/teuton/case/case.rb +2 -0
  42. data/lib/teuton/case/close.rb +4 -4
  43. data/lib/teuton/case/dsl/event.rb +1 -20
  44. data/lib/teuton/case/dsl/expect.rb +1 -1
  45. data/lib/teuton/case/dsl/log.rb +8 -3
  46. data/lib/teuton/case/dsl/target.rb +6 -1
  47. data/lib/teuton/case_manager/show_resume_report.rb +2 -2
  48. data/lib/teuton/check/checker.rb +2 -0
  49. data/lib/teuton/check/dsl/all.rb +8 -22
  50. data/lib/teuton/check/dsl/event.rb +10 -0
  51. data/lib/teuton/check/dsl/expect.rb +8 -16
  52. data/lib/teuton/check/dsl/target.rb +17 -0
  53. data/lib/teuton/check/show.rb +12 -12
  54. data/lib/teuton/cli/app.rb +15 -7
  55. data/lib/teuton/readme/dsl/all.rb +6 -1
  56. data/lib/teuton/report/formatter/default/txt.rb +9 -8
  57. data/lib/teuton/report/formatter/resume/txt.rb +5 -5
  58. data/lib/teuton/report/report.rb +2 -35
  59. data/lib/teuton/version.rb +1 -1
  60. metadata +11 -3
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # debug
4
4
 
@@ -50,32 +50,33 @@ end
50
50
 
51
51
  ```
52
52
  $ teuton examples/10-debug
53
+
53
54
  ------------------------------------
54
- Started at 2026-10-21 21:50:52 +0100
55
+ Started at 2026-10-26 16:30:56 +0000
55
56
 
56
57
  ******************** [DEBUG] count=6 ********************
57
58
  0: PING 192.168.1.1 (192.168.1.1) 56(84) bytes de datos.
58
- 1: 64 bytes desde 192.168.1.1: icmp_seq=1 ttl=64 tiempo=2.22 ms
59
+ 1: 64 bytes desde 192.168.1.1: icmp_seq=1 ttl=64 tiempo=5.11 ms
59
60
  2:
60
61
  3: --- 192.168.1.1 estadísticas ping ---
61
62
  4: 1 paquetes transmitidos, 1 recibidos, 0% packet loss, time 0ms
62
- 5: rtt min/avg/max/mdev = 2.220/2.220/2.220/0.000 ms
63
+ 5: rtt min/avg/max/mdev = 5.106/5.106/5.106/0.000 ms
63
64
  *********************************************************
64
65
  .
65
66
  ******************** [DEBUG] count=1 ********************
66
67
  0: 1
67
68
  *********************************************************
68
- FFFFFF
69
- Finished in 35.898 seconds
69
+ .FFFFF
70
+ Finished in 1.431 seconds
70
71
  ------------------------------------
71
72
 
72
- CASE RESULTS
73
+ ☰ CASE RESULTS
73
74
  ┌──────┬───────────┬───────┬───────┐
74
75
  │ CASE │ MEMBERS │ GRADE │ STATE │
75
- │ 01 │ student_1 │ 14.0 │ ? │
76
+ │ 01 │ student_1 │ 29.0 │ ? │
76
77
  └──────┴───────────┴───────┴───────┘
77
78
 
78
- CONN ERRORS
79
+ 🗲 CONN ERRORS
79
80
  ┌──────┬───────────┬─────────┬───────┐
80
81
  │ CASE │ MEMBERS │ HOST │ ERROR │
81
82
  │ 01 │ student_1 │ windows │ error │
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # export
4
4
 
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # preserve
4
4
 
@@ -31,11 +31,11 @@ Example, executing `teuton run example/12-preserve` twice:
31
31
  ```
32
32
  var
33
33
  └── 12-preserve
34
- ├── 20200519-113035
34
+ ├── 20200519-163035
35
35
  │   ├── case-01.txt
36
36
  │   ├── moodle.csv
37
37
  │   └── resume.txt
38
- ├── 20200519-123039
38
+ ├── 20200519-173039
39
39
  │   ├── case-01.txt
40
40
  │   ├── moodle.csv
41
41
  │   └── resume.txt
@@ -44,4 +44,4 @@ var
44
44
  └── resume.txt
45
45
  ```
46
46
 
47
- The first time test was launched at 11:30, and second at 12:30 the same day.
47
+ The first time test was launched at 16:30, and second at 17:30 the same day.
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # feedback
4
4
 
@@ -36,13 +36,13 @@ With `feedback: false` the fields are hidden: command, alterations, expected and
36
36
  Executing `teuton run examples/13-feedback`, we get this output:
37
37
 
38
38
  ```
39
- GROUPS
39
+ ☰ GROUPS
40
40
  ▶ Hide feedback messages from output
41
41
  ✔ 01 (1.0/1.0) ┌──
42
42
  Description │ Service SSH disabled
43
43
  Command │ *********************
44
44
  Output │ *********
45
- Duration │ 0.008 (local)
45
+ Duration │ 0.145 (local)
46
46
  Alterations │ **********************
47
47
  Expected │ **************
48
48
  Result │ ********
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # Moodle
4
4
 
@@ -12,7 +12,7 @@ Add a new field called `tt_moodle_id` to each case in "config.yaml". Fill it wit
12
12
 
13
13
  Example:
14
14
 
15
- ```
15
+ ```yaml
16
16
  global:
17
17
  cases:
18
18
  - tt_members: Darth Vader
@@ -31,7 +31,7 @@ We use `tt_moodle_max_score` to customize Moodle max score. So when Teuton expor
31
31
 
32
32
  ## Example
33
33
 
34
- ```
34
+ ```yaml
35
35
  global:
36
36
  tt_moodle_max_score: 10
37
37
  cases:
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # readme
4
4
 
@@ -11,12 +11,12 @@ Take a look at our test definition section (Group):
11
11
  ```ruby
12
12
  group "Customize readme output" do
13
13
 
14
- readme "This is our README example."
15
- readme "And here we'll see how to use readme keyword"
14
+ doc "This is our README example."
15
+ doc "And here we'll see how to use readme keyword"
16
16
 
17
17
  target "Create user david."
18
- readme "Help: you can use 'useradd' command to create users."
19
- readme "Remember: Only root is permitted to create new users."
18
+ doc "Help: you can use 'useradd' command to create users."
19
+ doc "Remember: Only root is permitted to create new users."
20
20
 
21
21
  run "id david"
22
22
  expect "david"
@@ -26,19 +26,19 @@ end
26
26
 
27
27
  > In this example, localhost's OS must be GNU/Linux (any other compatible OS) because the command used is `id david`.
28
28
 
29
- There exists some `readme` instructions after `group` and `target` lines.
29
+ There exists some `doc` instructions after `group` and `target` lines.
30
30
 
31
31
  ## Execution
32
32
 
33
- To generate automatically a README file from previous test, execute this:
33
+ To generate automatically a Markdown file from previous test, execute this:
34
34
 
35
35
  ```
36
- $ teuton readme example/15-readme > example/15-readme/README.md
36
+ $ teuton doc example/15-doc > example/15-doc/README.md
37
37
  ```
38
38
 
39
39
  ## Result
40
40
 
41
- Content of `example/15-readme/README.md` file.
41
+ Content of `example/15-doc/README.md` file.
42
42
 
43
43
  ---
44
44
 
@@ -49,7 +49,7 @@ Teuton : 3.2.1
49
49
 
50
50
  # Test: 15-readme
51
51
 
52
- ## Customize readme output
52
+ ## Customize doc output
53
53
  This is our readme example.
54
54
  And here we'll see how to use readme keyword
55
55
 
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # include
4
4
 
@@ -58,8 +58,8 @@ Config files into `moreconfigfiles` folder:
58
58
 
59
59
  If we execute the test we will see that 3 cases are processed. Which are defined in the files `file01.yaml`, `02/file02.yaml` and `file03.yml`.
60
60
 
61
- ```
62
- CASE RESULTS
61
+ ```
62
+ ☰ CASE RESULTS
63
63
  ┌──────┬────────────┬───────┬───────┐
64
64
  │ CASE │ MEMBERS │ GRADE │ STATE │
65
65
  │ 01 │ student_02 │ 0.0 │ ? │
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # alias
4
4
 
@@ -50,4 +50,4 @@ cases:
50
50
  username: obiwan
51
51
  ```
52
52
 
53
- Now our test will work correctly. Calling `get(:super)` will return the same value as doing `get(:superuser).
53
+ Now our test will work correctly. Calling `get(:super)` will return the same value as doing `get(:superuser)`.
data/docs/learn/18-log.md CHANGED
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # log
4
4
 
@@ -53,7 +53,8 @@ end
53
53
  Content of `var/18-log/case-01.txt` file.
54
54
 
55
55
  ```txt
56
- ❯ cat var/18-log/case-01.txt
56
+ $ cat var/18-log/case-01.txt
57
+
57
58
  CONFIGURATION
58
59
  ┌────────────────┬───────────────────────────────────┐
59
60
  │ tt_config_path │ examples/18-log/config.yaml │
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # Read vars
4
4
 
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # Macros
4
4
 
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # Example: exit_codes
4
4
 
@@ -1,4 +1,4 @@
1
- [<< back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # result
4
4
 
@@ -1,4 +1,4 @@
1
- [<<back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # Test code
4
4
 
@@ -1,4 +1,4 @@
1
- [<<back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # Test SQL and database content
4
4
 
@@ -1,4 +1,4 @@
1
- [<<back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # expect vs result
4
4
 
@@ -1,4 +1,4 @@
1
- [<<back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # DSL: expect_sequence
4
4
 
@@ -1,4 +1,4 @@
1
- [<<back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # DSL: run_script
4
4
 
@@ -1,9 +1,11 @@
1
- [<<back](README.md)
1
+ [<<back](../../README.md)
2
2
 
3
3
  # DSL: upload
4
4
 
5
5
  `upload` is a dsl instruction, whose purpose is to upload files to the remote host.
6
6
 
7
+ > **Recommendation**: Use this function as little as possible, to minimize any modifications, changes, or alterations to the remote device.
8
+
7
9
  ```ruby
8
10
  upload "FILENAME", to: :host1
9
11
  ```
@@ -0,0 +1,134 @@
1
+ [<<back](../../README.md)
2
+
3
+ # Event: stop
4
+
5
+ Sometimes it may be necessary to trigger an action when a specific condition occurs; for this, we can use events.
6
+
7
+ Events are defined with the following structure:
8
+
9
+ ```ruby
10
+ event on: OBJECT, when: CONDITION do
11
+ ACTION
12
+ end
13
+ ```
14
+
15
+ Where
16
+ * OBJECT can be `:last_target`
17
+ * CONDITION can be `:failed`
18
+ * And ACTION can be `stop`, `scores_to_zero`, or both.
19
+
20
+ ## Example
21
+
22
+ In this example, we have 4 targets, all of which will function correctly except for target 3.
23
+
24
+ ```ruby
25
+ group "Events: stop" do
26
+ target "Target 1: ok"
27
+ run "id root"
28
+ expect_ok
29
+
30
+ target "Target 2: ok"
31
+ run "id root"
32
+ expect_ok
33
+
34
+ target "Target 3: fail"
35
+ run "id vader"
36
+ expect_ok
37
+
38
+ event on: :last_target, when: :failed do
39
+ stop
40
+ end
41
+
42
+ target "Target 4: ok"
43
+ run "id root"
44
+ expect_ok
45
+ end
46
+ ```
47
+
48
+ An event is defined to trigger the `stop` action when the last target (target 3) fails.
49
+
50
+ The "stop" action halts the test, setting the number of targets yet to be evaluated to zero, while the rest retain their current assessment.
51
+
52
+ ## Report output
53
+
54
+ ```
55
+ $ teuton examples/31-event-stop
56
+
57
+ 🗹 CASE RESULTS
58
+ ┌──────┬──────────┬───────┬───────┐
59
+ │ CASE │ MEMBERS │ GRADE │ STATE │
60
+ │ 01 │ TOCHANGE │ 50.0 │ │
61
+ └──────┴──────────┴───────┴───────┘
62
+ ```
63
+
64
+ Process:
65
+ * target 1 is Ok.
66
+ * target 2 is Ok.
67
+ * target 3 fails so the `stop` action is triggered.
68
+ * target 4 is not evaluated because test was stopped.
69
+
70
+ ```
71
+ $ cat var/31-event-stop/case-01.txt
72
+
73
+ ⚒ CONFIGURATION
74
+ ┌────────────────┬────────────────────────────────────┐
75
+ │ tt_config_path │ examples/31-event-stop/config.yaml │
76
+ │ tt_members │ TOCHANGE │
77
+ │ tt_script_path │ examples/31-event-stop/start.rb │
78
+ │ tt_sequence │ false │
79
+ │ tt_skip │ false │
80
+ └────────────────┴────────────────────────────────────┘
81
+
82
+ 🛈 LOGS
83
+ [16:27:43] INFO| Event[0] triggered because target[3] failed
84
+ [16:27:43] INFO| Test STOPPED!
85
+
86
+ ☰ GROUPS
87
+ ▶ Events: stop
88
+ ✔ 01 (1.0/1.0) ┌──
89
+ Description │ Target 1: ok
90
+ Command │ id root
91
+ Output │ uid=0(root) gid=0(root) grupos=0(root)
92
+ Duration │ 0.002 (local)
93
+ Alterations │ Read exit code
94
+ Expected │ 0
95
+ Result │ 0
96
+ ✔ 02 (1.0/1.0) ├──
97
+ Description │ Target 2: ok
98
+ Command │ id root
99
+ Output │ uid=0(root) gid=0(root) grupos=0(root)
100
+ Duration │ 0.001 (local)
101
+ Alterations │ Read exit code
102
+ Expected │ 0
103
+ Result │ 0
104
+ ✖ 03 (0.0/1.0) ├──
105
+ Description │ Target 3: fail
106
+ Command │ id vader
107
+ Output │ id: «vader»: no existe ese usuario
108
+ Duration │ 0.002 (local)
109
+ Alterations │ Read exit code
110
+ Expected │ 0
111
+ Result │ 1
112
+ ✖ 04 (0.0/1.0) ├──
113
+ Description │ TEST STOPPED!
114
+ Command │
115
+ Output │
116
+ Duration │ ? (?)
117
+ Alterations │
118
+ Expected │
119
+ Result │
120
+ └──
121
+ 🗹 RESULTS
122
+ ┌──────────────┬───────────────────────────┐
123
+ │ unique_fault │ 0 │
124
+ │ case_id │ 01 │
125
+ │ start_time │ 2026-10-30 16:27:43 +0000 │
126
+ │ finish_time │ 2026-10-30 16:27:43 +0000 │
127
+ │ duration │ 0.006686742 │
128
+ │ max_weight │ 4.0 │
129
+ │ good_weight │ 2.0 │
130
+ │ fail_weight │ 2.0 │
131
+ │ fail_counter │ 2 │
132
+ │ grade │ 50 │
133
+ └──────────────┴───────────────────────────┘
134
+ ```
@@ -0,0 +1,127 @@
1
+ [<<back](../../README.md)
2
+
3
+ # Event: scores to zero
4
+
5
+ We are going to create an event that triggers when the last target fails, and which sets all the scores for the targets already evaluated to zero.
6
+
7
+ ```ruby
8
+ event on: :last_target, when: failed do
9
+ scores_to_zero
10
+ end
11
+ ```
12
+
13
+ ## Example
14
+
15
+ In this example, we have 4 targets, all of which will function correctly except for target 3.
16
+
17
+ ```ruby
18
+ group "Events: scores_to_zero" do
19
+ target "Target 1: ok"
20
+ run "id root"
21
+ expect_ok
22
+
23
+ target "Target 2: ok"
24
+ run "id root"
25
+ expect_ok
26
+
27
+ target "Target 3: fail"
28
+ run "id vader"
29
+ expect_ok
30
+
31
+ event on: :last_target, when: :failed do
32
+ scores_to_zero
33
+ end
34
+
35
+ target "Target 4: ok"
36
+ run "id root"
37
+ expect_ok
38
+ end
39
+ ```
40
+
41
+ The event, before target 3, is defined to trigger the `scores_to_zero` action when target fails.
42
+
43
+ The action resets the values ​​of all already-evaluated targets to zero.
44
+
45
+ ## Report output
46
+
47
+ ```
48
+ $ teuton examples/32-event-zero
49
+
50
+ ☰ CASE RESULTS
51
+ ┌──────┬──────────┬───────┬───────┐
52
+ │ CASE │ MEMBERS │ GRADE │ STATE │
53
+ │ 01 │ TOCHANGE │ 25.0 │ ? │
54
+ └──────┴──────────┴───────┴───────┘
55
+ ```
56
+
57
+ Process:
58
+ * target 1 is Ok.
59
+ * target 2 is Ok.
60
+ * target 3 fails so the `scores_to_zero` action is triggered. Targets 1 and 2 are reset to zero.
61
+ * target 4 is Ok.
62
+
63
+ ```
64
+ $ cat var/32-event-zero/case-01.txt
65
+
66
+ ⚒ CONFIGURATION
67
+ ┌────────────────┬────────────────────────────────────┐
68
+ │ tt_config_path │ examples/32-event-zero/config.yaml │
69
+ │ tt_members │ TOCHANGE │
70
+ │ tt_script_path │ examples/32-event-zero/start.rb │
71
+ │ tt_sequence │ false │
72
+ │ tt_skip │ false │
73
+ └────────────────┴────────────────────────────────────┘
74
+
75
+ 🛈 LOGS
76
+ [16:27:47] INFO| Event[0] triggered because target[3] failed
77
+ [16:27:47] INFO| Scores READJUSTED!
78
+
79
+ ☰ GROUPS
80
+ ▶ Events: scores_to_zero
81
+ ✔ 01 (0.0/1.0) ┌──
82
+ Description │ Target 1: ok (SCORE TO ZERO)
83
+ Command │ id root
84
+ Output │ uid=0(root) gid=0(root) grupos=0(root)
85
+ Duration │ 0.002 (local)
86
+ Alterations │ Read exit code
87
+ Expected │ 0
88
+ Result │ 0
89
+ ✔ 02 (0.0/1.0) ├──
90
+ Description │ Target 2: ok (SCORE TO ZERO)
91
+ Command │ id root
92
+ Output │ uid=0(root) gid=0(root) grupos=0(root)
93
+ Duration │ 0.001 (local)
94
+ Alterations │ Read exit code
95
+ Expected │ 0
96
+ Result │ 0
97
+ ✖ 03 (0.0/1.0) ├──
98
+ Description │ Target 3: fail (SCORE TO ZERO)
99
+ Command │ id vader
100
+ Output │ id: «vader»: no existe ese usuario
101
+ Duration │ 0.002 (local)
102
+ Alterations │ Read exit code
103
+ Expected │ 0
104
+ Result │ 1
105
+ ✔ 04 (1.0/1.0) ├──
106
+ Description │ Target 4: ok
107
+ Command │ id root
108
+ Output │ uid=0(root) gid=0(root) grupos=0(root)
109
+ Duration │ 0.001 (local)
110
+ Alterations │ Read exit code
111
+ Expected │ 0
112
+ Result │ 0
113
+ └──
114
+ 🗹 RESULTS
115
+ ┌──────────────┬───────────────────────────┐
116
+ │ unique_fault │ 0 │
117
+ │ case_id │ 01 │
118
+ │ start_time │ 2026-10-30 16:27:47 +0000 │
119
+ │ finish_time │ 2026-10-30 16:27:47 +0000 │
120
+ │ duration │ 0.006894103 │
121
+ │ max_weight │ 4.0 │
122
+ │ good_weight │ 3.0 │
123
+ │ fail_weight │ 1.0 │
124
+ │ fail_counter │ 1 │
125
+ │ grade │ 25 │
126
+ └──────────────┴───────────────────────────┘
127
+ ```
data/docs/more.md CHANGED
@@ -9,7 +9,10 @@
9
9
 
10
10
  # Videos
11
11
 
12
- > By now there are no English videos. We are sorry!
12
+ > By now there are no English videos. Sorry!
13
+
14
+ Teuton v3.0:
15
+ * [RubySur Agosto 2026 - Test de Infraestructura Teuton](https://www.youtube.com/live/JW4T3cTyG5A?si=bt356lypKlrr_YLK)
13
16
 
14
17
  Teuton v2.1:
15
18
  * [Apuntes FP Informática - I Congreso Virtual - Mayo 2020](https://youtu.be/RxIV26BAoGo)