teuton 2.10.8 → 2.11.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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +65 -18
  3. data/docs/commands/README.md +18 -81
  4. data/docs/commands/check.md +66 -0
  5. data/docs/commands/config.md +120 -0
  6. data/docs/commands/help.md +37 -0
  7. data/docs/commands/new.md +34 -0
  8. data/docs/commands/readme.md +52 -0
  9. data/docs/commands/{run-tests.md → run.md} +1 -1
  10. data/docs/config_file.md +125 -0
  11. data/docs/devel/README.md +12 -0
  12. data/docs/dsl/expect.md +2 -2
  13. data/docs/dsl/run.md +30 -67
  14. data/docs/install/s-node.md +9 -7
  15. data/docs/install/t-node.md +18 -19
  16. data/docs/learn/11-export.md +1 -1
  17. data/docs/learn/13-feedback.md +1 -1
  18. data/docs/learn/14-moodle_id.md +4 -4
  19. data/docs/learn/15-readme.md +14 -8
  20. data/docs/learn/16-include.md +21 -16
  21. data/docs/learn/17-alias.md +2 -2
  22. data/docs/learn/18-log.md +5 -3
  23. data/docs/learn/19-read_vars.md +7 -3
  24. data/docs/learn/20-macros.md +6 -4
  25. data/docs/learn/21-exit_codes.md +3 -3
  26. data/docs/learn/22-result.md +7 -3
  27. data/docs/learn/23-test-code.md +17 -7
  28. data/docs/learn/24-test-sql.md +19 -18
  29. data/docs/learn/25-expect-result.md +1 -0
  30. data/docs/learn/26-expect_sequence.md +12 -7
  31. data/docs/learn/27-run_script.md +11 -5
  32. data/docs/tutorial/es/nginx/README.md +546 -0
  33. data/lib/teuton/case/case.rb +0 -2
  34. data/lib/teuton/case/dsl/expect.rb +2 -2
  35. data/lib/teuton/case/dsl/log.rb +1 -1
  36. data/lib/teuton/case/dsl/run.rb +2 -3
  37. data/lib/teuton/case/dsl/upload.rb +1 -1
  38. data/lib/teuton/case/execute/execute_base.rb +3 -6
  39. data/lib/teuton/case/execute/execute_local.rb +5 -2
  40. data/lib/teuton/case/execute/execute_ssh.rb +0 -1
  41. data/lib/teuton/case/execute/execute_telnet.rb +0 -2
  42. data/lib/teuton/case/play.rb +2 -2
  43. data/lib/teuton/case/result/result.rb +1 -3
  44. data/lib/teuton/case_manager/case_manager.rb +14 -17
  45. data/lib/teuton/case_manager/dsl.rb +4 -4
  46. data/lib/teuton/case_manager/export_manager.rb +22 -17
  47. data/lib/teuton/case_manager/{check_cases.rb → ext/check_cases.rb} +7 -6
  48. data/lib/teuton/case_manager/ext/hall_of_fame.rb +28 -0
  49. data/lib/teuton/case_manager/{report.rb → ext/report.rb} +6 -8
  50. data/lib/teuton/case_manager/send_manager.rb +1 -0
  51. data/lib/teuton/case_manager/{show_report.rb → show_resume_report.rb} +12 -26
  52. data/lib/teuton/case_manager/stats_manager.rb +26 -0
  53. data/lib/teuton/check/checker.rb +16 -8
  54. data/lib/teuton/check/main.rb +2 -2
  55. data/lib/teuton/check/show.rb +3 -3
  56. data/lib/teuton/cli.rb +3 -2
  57. data/lib/teuton/readme/dsl/getset.rb +1 -0
  58. data/lib/teuton/readme/main.rb +2 -2
  59. data/lib/teuton/readme/readme.rb +2 -2
  60. data/lib/teuton/report/formatter/default/array.rb +1 -3
  61. data/lib/teuton/report/formatter/default/markdown.rb +1 -1
  62. data/lib/teuton/report/formatter/default/txt.rb +1 -1
  63. data/lib/teuton/report/formatter/default/xml.rb +93 -29
  64. data/lib/teuton/report/formatter/formatter.rb +2 -1
  65. data/lib/teuton/report/formatter/resume/array.rb +1 -3
  66. data/lib/teuton/report/formatter/resume/txt.rb +6 -2
  67. data/lib/teuton/report/formatter/resume/xml.rb +92 -0
  68. data/lib/teuton/report/report.rb +1 -1
  69. data/lib/teuton/utils/config_file_reader.rb +141 -0
  70. data/lib/teuton/utils/name_file_finder.rb +12 -15
  71. data/lib/teuton/utils/project.rb +19 -21
  72. data/lib/teuton/utils/settings.rb +3 -1
  73. data/lib/teuton/version.rb +1 -1
  74. data/lib/teuton.rb +11 -10
  75. metadata +35 -30
  76. data/docs/commands/check-example.md +0 -53
  77. data/docs/es/aprender/01-cmd_new.md +0 -27
  78. data/docs/es/aprender/02-target.md +0 -131
  79. data/docs/es/aprender/README.md +0 -36
  80. data/docs/ideas/todo.md +0 -44
  81. data/docs/install/README.md +0 -38
  82. data/lib/teuton/case_manager/hall_of_fame.rb +0 -29
  83. data/lib/teuton/files/README.md +0 -9
  84. data/lib/teuton/utils/configfile_reader.rb +0 -135
  85. /data/docs/{ideas → devel}/contributions.md +0 -0
  86. /data/docs/{ideas → es}/Challenge-Server-Project.md +0 -0
  87. /data/docs/{ideas → es}/servidor-de-retos.md +0 -0
  88. /data/docs/{install/modes_of_use.md → modes_of_use.md} +0 -0
  89. /data/lib/teuton/{case_manager → deprecated}/utils.rb +0 -0
@@ -0,0 +1,546 @@
1
+ [<< back](../../../README.md)
2
+
3
+ # Tutorial: Nginx Web Server
4
+
5
+ Vamos a hacer un tutorial de cómo crear un test para checkear la instalación de un servidor web Nginx.
6
+
7
+ Esquema de clase:
8
+ ```mermaid
9
+ graph TD
10
+ %% Definición de los nodos
11
+ P["profesor(teuton)"]
12
+ R(( Network ))
13
+ A1["alumno1(ssh)"]
14
+ A2["alumno2(ssh)"]
15
+ An["alumnoN(ssh)"]
16
+
17
+ %% Conexiones
18
+ P --- R
19
+ R --- A1
20
+ R --- A2
21
+ R --- An
22
+
23
+ %% Estilos (Opcional)
24
+ style P fill:#aaffaa,stroke:#006400,stroke-width:2px,color:#000
25
+ style R fill:#fff,stroke:#333,stroke-dasharray: 5 5
26
+ ```
27
+
28
+ ## 1. Consultamos a la IA generativa
29
+
30
+ _¿Cómo no? En estos tiempos todo el mundo está hablando de la IA generativa. Así que vamos a usarla para que nos ayude con el test de Teuton? ;-)_
31
+
32
+ * Abro Gemini y le pongo el siguiente prompt:
33
+ ```
34
+ Usando la siguiente plantilla de ejemplo de la herramienta Teuton:
35
+
36
+ group "Learn about targets" do
37
+ target "Create user obiwan", weight: 2
38
+ run "id obiwan"
39
+ expect ["uid=", "(obiwan)", "gid="]
40
+
41
+ target "Delete user vader"
42
+ run "id vader"
43
+ expect_fail
44
+ end
45
+
46
+ start do
47
+ show
48
+ export
49
+ end
50
+
51
+ quiero que hagas un test para evaluar que tenemos activo el servicio web nginx
52
+ y que la página principal index.html tiene en su contenido el texto "Hola Mundo!"
53
+ ```
54
+
55
+ * La respuesta de Gemini es un buen inicio:
56
+ ```ruby
57
+ group "Nginx Web Service Checks" do
58
+
59
+ # Objetivo 1: Verificar que el servicio Nginx está activo
60
+ # Se asume que el comando 'systemctl status' está disponible (en sistemas Linux basados en systemd)
61
+ target "Check Nginx service status", weight: 3
62
+
63
+ # Comando para verificar el estado del servicio
64
+ run "systemctl status nginx"
65
+
66
+ # Expectativa: La salida del comando debe contener el texto "Active: active (running)"
67
+ # para confirmar que el servicio está en ejecución.
68
+ expect ["Active: active (running)"]
69
+
70
+
71
+ # Objetivo 2: Verificar el contenido de la página principal (index.html)
72
+ # Se asume que la página index.html está en el directorio raíz de Nginx (/var/www/html/ o similar)
73
+ target "Check index.html content for 'Hola Mundo!'", weight: 5
74
+
75
+ # Comando para leer el contenido del archivo de la página principal
76
+ # NOTA: Ajusta la ruta del archivo si tu configuración Nginx es diferente.
77
+ run "cat /var/www/html/index.html"
78
+
79
+ # Expectativa: El contenido del archivo debe incluir el texto exacto "Hola Mundo!"
80
+ expect ["Hola Mundo!"]
81
+ end
82
+
83
+ start do
84
+ show
85
+ export
86
+ end
87
+ ```
88
+
89
+ ## 2. Refinamos manualmente
90
+
91
+ Vamos a realizar algunos cambios manualmente:
92
+ * Vamos a poner los textos en español.
93
+ * Los textos de los comentarios que contengan información útil para el alumno, nos puede interesar incluirlos en el enunciado de la práctica. Para ello usamos la instrucción `readme`.
94
+ * El resto de los comentarios, me parecen evidentes, los elimino.
95
+ * En la instrucción `expect` podemos quitar los corchetes si sólo tenemos un filtro. Así, es más fácil de leer.
96
+ * El `readme` después del `group`es una aclaración de información del grupo, y el `readme` después del `target` es una aclaración de ese target concreto.
97
+
98
+ ```ruby
99
+ group "Comprobar el servicio web Nginx" do
100
+ readme "Necesitamos un SO GNU/Linux basado en systemd. Por ejemplo: OpenSUSE, Debian, etc"
101
+
102
+ target "Comprobar el estado del servicio Nginx", weight: 3
103
+ run "systemctl status nginx"
104
+ expect "Active: active (running)"
105
+
106
+ target "Comprobar que index.html contiene el texto 'Hola Mundo!'", weight: 5
107
+ readme "Se asume que Nginx está instalado en su ruta por defecto"
108
+ run "cat /var/www/html/index.html"
109
+ expect "Hola Mundo!"
110
+ end
111
+
112
+ start do
113
+ show
114
+ export
115
+ end
116
+ ```
117
+
118
+ > **NOTA**: Mientras estamos haciendo cambios en el test podemos usar los siguientes comandos para comprobar que todo va funcionando correctamente.
119
+ > * `teuton readme PATH/TO/FOLDER` para ver cómo se genera el enunciado asociado a la práctica.
120
+ > * `teutob check PATH/TO/FOLDER` para comprobar que no hay fallos en la sintaxis, etc.
121
+
122
+ ## 3. Añadimos un fichero de configuración
123
+
124
+ * Para tener mayor legibilidad en el futuro, el primer refinamiento que vamos a realizar es separar los tests específicos de Nginx de las instrucciones del script principal.
125
+ 1. `start.rb`: Script principal
126
+ 2. `nginx.rb`: Tests específicos de Nginx
127
+
128
+ ```ruby
129
+ # File: start.rb (Script principal)
130
+ use "nginx"
131
+
132
+ start do
133
+ show
134
+ export
135
+ end
136
+
137
+ ```
138
+
139
+ ```ruby
140
+ # File: nginx.rb (Tests específicos de Nginx)
141
+
142
+ group "Comprobar el servicio web Nginx" do
143
+ readme "* Necesitamos un SO GNU/Linux basado en systemd. Por ejemplo: OpenSUSE, Debian, etc."
144
+
145
+ target "Comprobar el estado del servicio Nginx", weight: 3
146
+ run "systemctl status nginx"
147
+ expect "Active: active (running)"
148
+
149
+ target "Comprobar que index.html contiene el texto 'Hola Mundo!'", weight: 5
150
+ readme "Se asume que Nginx está instalado en su ruta por defecto."
151
+ run "cat /var/www/html/index.html"
152
+ expect "Hola Mundo!"
153
+ end
154
+ ```
155
+
156
+ Ahora mismo, el test se ejecuta directamente en la máquina `localhost`, pero vamos a modificarlo para que se pueda ejecutar en las máquinas remotas de nuestros alumnos.
157
+ * Modificamos la instrucción `run` para indicar dónde se tiene que ejecutar el comando: `run COMMAND, on: :webserver`. El nombre de host `webserver` es completamente arbitrario. Lo ideal es poner algo significativo para nosotros como: `nginx` `server`, `host1`, `linux`, etc. Cualquiera valdría.
158
+
159
+ ```ruby
160
+ # File: nginx.rb (Tests específicos de Nginx)
161
+
162
+ group "Comprobar el servicio web Nginx" do
163
+ readme "* Necesitamos un SO GNU/Linux basado en systemd. Por ejemplo: OpenSUSE, Debian, etc."
164
+
165
+ target "Comprobar el estado del servicio Nginx", weight: 3
166
+ run "systemctl status nginx", on: :webserver
167
+ expect "Active: active (running)"
168
+
169
+ target "Comprobar que index.html contiene el texto 'Hola Mundo!'", weight: 5
170
+ readme "Se asume que Nginx está instalado en su ruta por defecto."
171
+ run "cat /var/www/html/index.html", on: :webserver
172
+ expect "Hola Mundo!"
173
+ end
174
+ ```
175
+
176
+ Tenemos que incluir un fichero de configuración para especificar la configuración específica de cada una de las máquinas de nuestros alumnos, que son las que queremos evaluar realmente.
177
+
178
+ * A partir del contenido del test, Teuton es capaz de deducir los parámetros que se necesitan para su ejecución. Para ello usamos el comando `teuton config PATH/FOLDER`.
179
+
180
+ ```yaml
181
+ ---
182
+ global:
183
+ cases:
184
+ - tt_members: TOCHANGE
185
+ webserver_ip: TOCHANGE
186
+ webserver_username: TOCHANGE
187
+ webserver_password: TOCHANGE
188
+ ```
189
+
190
+ * Personalizamos los valores de los parámetros:
191
+
192
+ ```yaml
193
+ ---
194
+ global:
195
+ cases:
196
+ # Máquina del alumno 1
197
+ - tt_members: Alumno 1
198
+ webserver_ip: 192.168.122.254
199
+ webserver_username: user
200
+ webserver_password: secret
201
+ ```
202
+
203
+ ## 4. Ejecutamos el test sobre las máquinas remotas
204
+
205
+ * Ahora vamos a ejecutar el test `teuton run PATH/TO/FOLDER`.
206
+
207
+ ```
208
+ ------------------------------------
209
+ Started at 2025-12-05 23:10:38 +0000
210
+ FF
211
+ Finished in 0.674 seconds
212
+ ------------------------------------
213
+
214
+ CASE RESULTS
215
+ +------+----------+-------+-------+
216
+ | CASE | MEMBERS | GRADE | STATE |
217
+ | 01 | Alumno 1 | 0.0 | ? |
218
+ +------+----------+-------+-------+
219
+ ```
220
+
221
+ El alumno 1 tiene una nota final de 0. Esto es porque todavía no ha realizado la práctica.
222
+
223
+ Como profesores, mientras estamos diseñando el test, es muy útil tener una MV que haga de la máquina del alumno para ir haciendo las pruebas durante el proceso. Por motivos didácticos, vamos a añadir una segunda MV (del alumno2), para simular que tenemos un grupo de clase donde tenemos que a un alumno le sale la práctica mal y a otro bien.
224
+
225
+ * Ampliamos el fichero de configuración con 2 cases:
226
+
227
+ ```yaml
228
+ ---
229
+ global:
230
+ cases:
231
+ # Máquina del alumno 1
232
+ - tt_members: Alumno 1
233
+ webserver_ip: 192.168.122.254
234
+ webserver_username: user
235
+ webserver_password: secret
236
+ # Máquina del alumno 2
237
+ - tt_members: Alumno 2
238
+ webserver_ip: 192.168.122.108
239
+ webserver_username: user
240
+ webserver_password: secret
241
+ ```
242
+
243
+ > **NOTA**:
244
+ > * Los valores de las IP son los de mis MV ahora pero pueden ser diferentes.
245
+ > * Estoy usando el hipervisor KVM para crear las MV dentro de máquina real, pero se pueden usar otros como VirtualBox, Qemu, Parallel, Hyper-V, incluso contenedores con SSH en ejecución, máquinas reales, Raspberry PI, etc.
246
+
247
+ * Ejecutamos el test. De momento todas las notas están a 0 porque los alumnos todavía no han realizado la práctica:
248
+ ```
249
+ $ teuton run nginx/v02
250
+ ------------------------------------
251
+ Started at 2025-12-06 16:25:10 +0000
252
+ FFFF
253
+ Finished in 0.601 seconds
254
+ ------------------------------------
255
+
256
+ CASE RESULTS
257
+ +------+----------+-------+-------+
258
+ | CASE | MEMBERS | GRADE | STATE |
259
+ | 01 | Alumno 1 | 0.0 | ? |
260
+ | 02 | Alumno 2 | 0.0 | ? |
261
+ +------+----------+-------+-------+
262
+ ```
263
+
264
+ ## 5. Optimizando el fichero de configuración
265
+
266
+ Ahora sólo tenemos dos alumnos (cases) en el fichero de configuración, pero sabemos que este número va a aumentar bastante cuando lo llevemos al aula. También vemos que tenemos unos parámetros con valores repetidos en cada case: `webserver_username: user` y `webserver_password: secret`.
267
+
268
+ Para no repetirnos (_DRY: Don't repeat Yourself_) usamos la sección `global` de la siguiente forma:
269
+
270
+ ```yaml
271
+ ---
272
+ global:
273
+ webserver_username: user
274
+ webserver_password: secret
275
+ cases:
276
+ # Máquina del alumno 1
277
+ - tt_members: Alumno 1
278
+ webserver_ip: 192.168.122.254
279
+ # Máquina del alumno 2
280
+ - tt_members: Alumno 2
281
+ webserver_ip: 192.168.122.108
282
+ ```
283
+
284
+ ## 6. Empezamos con la práctica
285
+
286
+ Por motivos didácticos, me voy a convertir en el alumno2 y voy a ir haciendo la práctica sólo en su MV para que pueda obtener la máxima puntuación mientras que el alumno1 se quedará en 0 (_Sólo es una simulación_).
287
+
288
+ * Voy a la MV de alumno2.
289
+ * Lo primero que pide el enunciado es instalar el servidor web nginx. Como al alumno2 tiene una SO Debian, haremos como root `apt install nginx`.
290
+ * Ejecutamos los tests para ver el cambio:
291
+
292
+ ```
293
+ $ teuton run nginx/v02
294
+ ------------------------------------
295
+ Started at 2025-12-06 16:41:11 +0000
296
+ F.FF
297
+ Finished in 0.581 seconds
298
+ ------------------------------------
299
+
300
+ CASE RESULTS
301
+ +------+----------+-------+-------+
302
+ | CASE | MEMBERS | GRADE | STATE |
303
+ | 01 | Alumno 1 | 0.0 | ? |
304
+ | 02 | Alumno 2 | 38.0 | ? |
305
+ +------+----------+-------+-------+
306
+ ```
307
+
308
+ El alumno2 ha subido un poco la nota, pero tiene un valor extraño de un 38%, o lo que es lo mismo un 3,8. _¿De dónde sale ese valor?_
309
+
310
+ ## 7. Puntuaciones y pesos
311
+
312
+ El test ahora mismo tiene los siguientes objetivos (`targets`)
313
+
314
+ | Target | Descripción | Peso |
315
+ | ------ | -------------------------------------------------------- | ---- |
316
+ | 01 | Comprobar el estado del servicio Nginx | 3 |
317
+ | 02 | Comprobar que index.html contiene el texto 'Hola Mundo!' | 5 |
318
+
319
+ Si se completa el 100% de los targets entonces se obtiene un total de 8 puntos (3+5), por lo tanto, si sólo se completan 3 de 8 tenemos un grado de cumplimiento del 38% (3,8).
320
+ ```
321
+ Fórmula aplicada : (3/8) * 100 = 37,5
322
+ Redondeando nos queda : 38
323
+ ```
324
+
325
+ Es muy posible que no gusten los valores actuales de los pesos (`weight`), es normal, de hecho no los hemos puesto nosotros. Fue Gemini, en respuesta a nuestro prompt, el que nos hizo esa sugerencia.
326
+
327
+ * Cambiemos los pesos según nuestro criterio. Si no sabemos que valores poner, entonces no pongas pesos y por defecto todos los targets tendrán el mismo peso (weight: 1). Como profesor, lo normal es darle más peso a los targets que consideramos más importantes, pero esto lo iremos ajustando a medida que dominamos la materia que enseñamos.
328
+
329
+ ```
330
+ ...
331
+ target "Comprobar el estado del servicio Nginx", weight: 4
332
+ ...
333
+ target "Comprobar que index.html contiene el texto 'Hola Mundo!'", weight: 6
334
+ ...
335
+ ```
336
+
337
+ ```
338
+ $ teuton run nginx/v02
339
+ ------------------------------------
340
+ Started at 2025-12-06 16:53:14 +0000
341
+ .FFF
342
+ Finished in 0.542 seconds
343
+ ------------------------------------
344
+
345
+ CASE RESULTS
346
+ +------+----------+-------+-------+
347
+ | CASE | MEMBERS | GRADE | STATE |
348
+ | 01 | Alumno 1 | 0.0 | ? |
349
+ | 02 | Alumno 2 | 40.0 | ? |
350
+ +------+----------+-------+-------+
351
+ ```
352
+
353
+ > Si no sabes que pesos poner, no pongas ninguno, o lo que es lo mismo que todos los targets tengan peso 1.
354
+
355
+ ## 8. Continuamos con la práctica
356
+
357
+ Seguimos haciendo la práctica en la MV del alumno2.
358
+ * Ahora toca resolver el segundo target: "Comprobar que index.html contiene el texto 'Hola Mundo!'".
359
+ * Ejecutamos el test:
360
+ ```
361
+ $ teuton run nginx/v02
362
+ ------------------------------------
363
+ Started at 2025-12-06 16:59:08 +0000
364
+ .F.F
365
+ Finished in 0.642 seconds
366
+ ------------------------------------
367
+
368
+ CASE RESULTS
369
+ +------+----------+-------+-------+
370
+ | CASE | MEMBERS | GRADE | STATE |
371
+ | 01 | Alumno 1 | 0.0 | ? |
372
+ | 02 | Alumno 2 | 100.0 | ✔ |
373
+ +------+----------+-------+-------+
374
+ ```
375
+
376
+ ## 9. Vamos a personalizar el test
377
+
378
+ Cuando empezamos con estas prácticas de laboratorio, es común, que el profesor prepare una MV base para los alumnos, y que luego se clone esta MV base para cada alumno. De modo que al empezar todos tienen exactamente el mismo entorno.
379
+
380
+ También podemos partir de una MV base común, pero donde cada alumno debe realizar una serie de personalizaciones para que las MV de cada alumno se diferencien entre sí y prevenir que un alumno presente una MV clonada de un compañero como trabajo propio.
381
+
382
+ De modo que vamos a añadir un poco de personalización a las máquinas. Los alumnos, como parte de la práctica, tendrán que personalizar sus máquinas para que cada uno tenga usuario y passwords diferentes.
383
+
384
+ * Modificamos el fichero de configuración para que los usuarios/passwords de cada MV sean diferentes:
385
+
386
+ ```yaml
387
+ ---
388
+ global:
389
+ cases:
390
+ # Máquina del alumno 1
391
+ - tt_members: Alumno 1
392
+ webserver_ip: 192.168.122.254
393
+ webserver_username: alumno1
394
+ webserver_password: secret1
395
+ # Máquina del alumno 2
396
+ - tt_members: Alumno 2
397
+ webserver_ip: 192.168.122.108
398
+ webserver_username: alumno2
399
+ webserver_password: secret2
400
+ ```
401
+
402
+ * Por motivos didácticos, vamos a quitar todos los pesos, o lo que es lo mismo que todos los targets tengan paso 1.
403
+ * Ampliamos la personalización, modificando el segundo target para que el texto de la página web sea diferente para cada alumno/MV, mostrando algo como "Hola Alumno 1!" para al alumno1 y "Hola Alumno 2" para el alumno2. Para ello cambiamos la instrucción `expect`.
404
+
405
+ ```ruby
406
+ target "Comprobar que index.html contiene el texto 'Hola Mundo!'"
407
+ readme "Se asume que Nginx está instalado en su ruta por defecto."
408
+ run "cat /var/www/html/index.html", on: :webserver
409
+ expect "Hola #{get(:tt_members)}!"
410
+ ```
411
+
412
+ La instrucción `expect` debe evaluar la presencia de un determinado contenido ("Hola STUDENT_NAME!") en la página web. Pero como el valor de STUDENT_NAME es diferente para cada case (alumno) no lo podemos poner fijo, sino que debemos leerlo de la configuración con la instrucción `get(:tt_members)`. De esta forma se obtiene un valor diferente para cada case (alumno).
413
+
414
+ > **SUGERENCIA**: Cuanta mayor personalización añadamos a nuestros tests, más complicado (pero no imposible) les será a los alumnnos "copiar" el trabajo de otros mediante el clonado de MV.
415
+
416
+ _El test está listo para llevarlo al aula, ahora nos falta completar el fichero de configuración con datos reales de nuestros alumnos._
417
+
418
+ ## 10. Completar la configuración con datos reales
419
+
420
+ Para nuestra simulación, mientras diseñábamos el test, creamos la configuración para 2 alumnos ficticios. Pero cuando querramos ejecutar el test en el aula hay que añadir las configuraciones de todos los alumnos.
421
+
422
+ Tenemos varias formas de hacerlo, según el estilo de cada docente:
423
+ * **Lo decide el profesor manualmente**: Una posibilidad es que pongamos la configuración de cada alumno según el criterio del profesor y los alumnos deben adaptar sus MV (IP, username, password) seǵun lo que haya determinado el profesor.
424
+ * **Lo decide el alumno y el profesor lo actualiza manualmente**: Otra opción es que cada alumno le indique al profesor los valores de configuración para que el profesor actualice el fichero de configuración manualmente. Esto es un poco "pesado" si hay muchos alumnos. Una posibilidad es que los alumnos envíen al profesor un YAML con su configuración, profesor los guarda en la carpeta `config.d` y mediante el parámetro `tt_include: config.d`, todos los ficheros en la carpeta se incluirán como parte de la configuración del test.
425
+ * **Lo comunica el alumno en remoteo y se actualiza automáticamente**: Una tercera opción es que el profesor utilice el servicio `teuton config --server PATH/TO/FOLDER`, y de esta forma cada alumno proporciona los valores de su configuración, los cuales se irán guardando automáticamente.
426
+
427
+ Veamos en detalle esta última opción.
428
+
429
+ ## 11. FUNCION EXPERIMENTAL: Servidor de configuración
430
+
431
+ * El profesor inicia el servidor de configuración:
432
+
433
+ ```
434
+ $ teuton config --server nginx/v04.final
435
+ --------------------------------------------------
436
+ ConfigServer URL: http://192.168.1.28:8080
437
+
438
+ Project path : nginx/v04.final
439
+ Global params (1)
440
+ * tt_include : config.d
441
+ Cases params (4)
442
+ * tt_members
443
+ * webserver_ip
444
+ * webserver_username
445
+ * webserver_password
446
+ --------------------------------------------------
447
+ == Sinatra (v4.2.1) has taken the stage on 8080 for development with backup from Puma
448
+ Puma starting in single mode...
449
+ * Puma version: 7.1.0 ("Neon Witch")
450
+ * Ruby version: ruby 3.2.8 (2025-03-26 revision 13f495dc2c) [x86_64-linux]
451
+ * Min threads: 0
452
+ * Max threads: 5
453
+ * Environment: development
454
+ * PID: 8268
455
+ * Listening on http://0.0.0.0:8080
456
+ Use Ctrl-C to stop
457
+ ```
458
+
459
+ * Cada alumno, desde su MV, abre el navegador con URL `http://TEACHER_IP:8080`
460
+ * Cada alumno, rellena el formulario web con sus datos:
461
+
462
+ ![](form.png)
463
+
464
+ * Cuando todos los alumnos envían sus datos, el profesor pulsa CTRL+C para cerrar el servicio de configuración remota. Vemos que se nos ha creado un subdirectorio dentro de nuestro test (`config.d`) donde se han ido guardando los ficheros con la configuración enviada por cada alumno. Cada envío remoto queda registrado con la IP desde donde se realizó.
465
+
466
+ ```
467
+ $ tree nginx/v04.final
468
+ nginx/v04.final
469
+ ├── config.d
470
+ │   ├── from_192.168.122.101.yaml
471
+ │   └── from_192.168.122.102.yaml
472
+ ├── config.yaml
473
+ ├── nginx.rb
474
+ └── start.rb
475
+ ```
476
+
477
+ * Además, en el fichero de configuración principal, se crea el parámetro `tt_include: config.d` para indicaar que además del contenido de `config.yaml` debemos incluir como parte de la configuración todos los ficheros del subdirectorio `config.d`.
478
+
479
+ ```yaml
480
+ ---
481
+ global:
482
+ tt_include: config.d
483
+ cases:
484
+ - tt_members: Alumno 1
485
+ webserver_ip: 192.168.122.254
486
+ webserver_username: alumno1
487
+ webserver_password: secret1
488
+ - tt_members: Alumno 2
489
+ webserver_ip: 192.168.122.108
490
+ webserver_username: alumno2
491
+ webserver_password: secret2
492
+ ```
493
+
494
+ _¡Ya tenemos el test y las configuraciones listas para trabajar en el aula!_
495
+
496
+ ## 12. Sesión de trabajo en el aula
497
+
498
+ Ya, tenemos listo el test y el fichero de configuración. Nos lo llevamos al aula para usarlo en producción.
499
+
500
+ Ejecutamos el test:
501
+ * **Al comienzo de la sesión**: Ejecutamos el test simplemente para validar que hay conectividad con todas las MV de los alumnos aunque en este momento todas las notas estén a 0.
502
+ * **Durante la sesión**: De forma opcional, podemos ir ejecutando el test para ir monitorizando cómo van avanzando los alumnos.
503
+ * **Al final de la sesión**: Al finalizar la sesión ejecutamos el test por última vez para quedarnos con el resultado final.
504
+
505
+ > Cada vez que se ejecuta el test y se guardan los resultados en `var/TESNAME/case-*.txt`, se sobreescriben los ficheros de ejecuciones anteriores.
506
+
507
+ ---
508
+
509
+ ## ANEXO: Problemas de conexión
510
+
511
+ Si al ejecutar el test nos encontramos con la siguiente salida:
512
+ ```
513
+ $ teuton run nginx/v03.custom
514
+ ------------------------------------
515
+ Started at 2025-12-06 20:12:16 +0000
516
+ FFFF
517
+ Finished in 30.033 seconds
518
+ ------------------------------------
519
+
520
+ CASE RESULTS
521
+ +------+----------+-------+-------+
522
+ | CASE | MEMBERS | GRADE | STATE |
523
+ | 01 | Alumno 1 | 0.0 | ? |
524
+ | 02 | Alumno 2 | 0.0 | ? |
525
+ +------+----------+-------+-------+
526
+
527
+ CONN ERRORS
528
+ +------+----------+-----------+-------+
529
+ | CASE | MEMBERS | HOST | ERROR |
530
+ | 01 | Alumno 1 | webserver | error |
531
+ | 02 | Alumno 2 | webserver | error |
532
+ +------+----------+-----------+-------+
533
+ ```
534
+
535
+ Vemos que todas las puntuaciones están a 0, y que aparecen errores de conexión a la máquina `webserver`de todos los alumnos. Para obtener más información sobre el problema de la conexión, podemos consultar el informe de cada alumno. Por ejemplo `var/TESTNAME/case-01.txt`.
536
+
537
+ ```
538
+ ...
539
+ LOGS
540
+ [20:12:46] ERROR: [Net::SSH::ConnectionTimeout] SSH on <alumno1@192.168.122.254> exec: systemctl status nginx
541
+ ...
542
+ ```
543
+
544
+ Podemos ver cómo en la sección LOGS nos dice que hay un error `SSH::ConnectionTimeout` al intentar acceder vía SSH con el equipo del alumno1.
545
+
546
+ El problema se debe a que los alumnos todavía no han encendido sus MV. Una vez que todos enciendan sus MV prodemos seguir con normalidad.
@@ -43,11 +43,9 @@ class Case
43
43
  @skip = true
44
44
  @skip = false if Project.value[:options]["case"].include? @id.to_i
45
45
  end
46
- @debug = Project.debug?
47
46
  @verbose = Project.value[:verbose]
48
47
 
49
48
  @tmpdir = File.join("var", @config.get(:tt_testname), "tmp", @id.to_s)
50
- # ensure_dir @tmpdir # REVISE: When we will need this? Samba?
51
49
 
52
50
  @unique_values = {}
53
51
  @result = Result.new
@@ -15,7 +15,7 @@ module DSL
15
15
  elsif input.instance_of?(String) || input.instance_of?(Regexp) || input.instance_of?(Array)
16
16
  expect_any input
17
17
  else
18
- puts Rainbow("[ERROR] Case expect TypeError: expect #{input} (#{input.class})").red
18
+ puts Rainbow("[ERROR] Case expect TypeError: <expect #{input} (#{input.class})>").red
19
19
  end
20
20
  end
21
21
 
@@ -37,7 +37,7 @@ module DSL
37
37
  @report.lines << @action.clone
38
38
  weight(1.0)
39
39
 
40
- c = Settings.letter[:bad]
40
+ c = Settings.letter[:fail]
41
41
  c = Settings.letter[:good] if cond
42
42
  verbose Rainbow(c).green
43
43
  end
@@ -15,5 +15,5 @@ module DSL
15
15
  msg = "[#{f}] #{text}" if s == ""
16
16
  @report.lines << msg
17
17
  end
18
- alias_method :msg, :log
18
+ # alias_method :msg, :log
19
19
  end
@@ -10,13 +10,12 @@ module DSL
10
10
  # @param args (Hash)
11
11
  def run(command, args = {})
12
12
  args[:exec] = command.to_s
13
- host = :localhost
14
- host = args[:on] if args[:on]
13
+ host = args[:on] || :localhost
15
14
  goto(host, args)
16
15
  end
17
16
 
18
17
  # Run command from the host identify as "host"
19
- # goto :host1, :execute => "command"
18
+ # goto :host1, execute: "command"
20
19
  def goto(host = :localhost, args = {})
21
20
  @result.reset
22
21
  args[:on] = host unless args[:on]
@@ -28,7 +28,7 @@ module DSL
28
28
  Net::SFTP.start(
29
29
  host.ip, host.username, password: host.password, port: host.port
30
30
  ) { |sftp| sftp.upload!(localpath, remotepath) }
31
- verbose(Rainbow("u").green)
31
+ verbose(Rainbow(Settings.letter(:upload)).green)
32
32
  rescue => e
33
33
  log("Upload #{localfile} to #{host.ip}:#{remotepath}", :warn)
34
34
  log(e.to_s, :warn)
@@ -1,8 +1,4 @@
1
- require_relative "../../utils/verbose"
2
-
3
1
  class ExecuteBase
4
- include Verbose
5
-
6
2
  def initialize(parent)
7
3
  @parent = parent
8
4
  # READ: @config, cmd = action[:command]
@@ -50,8 +46,9 @@ class ExecuteBase
50
46
  begin
51
47
  text = ec.convert(text)
52
48
  rescue => e
53
- puts "[ERROR] #{e}: Declare text encoding..."
54
- puts " run 'command', on: :host, encoding: 'ISO-8859-1'"
49
+ puts "[ERROR] ExecuteBase: #{e}"
50
+ puts "[ERROR] Declare text encoding. Example:"
51
+ puts "[ERROR] run 'command', on: :host, encoding: 'ISO-8859-1'"
55
52
  end
56
53
 
57
54
  text.split("\n").compact
@@ -5,6 +5,8 @@ require_relative "../../utils/verbose"
5
5
  require_relative "execute_base"
6
6
 
7
7
  class ExecuteLocal < ExecuteBase
8
+ include Verbose
9
+
8
10
  def call
9
11
  action[:conn_type] = :local
10
12
  response = my_execute(action[:command], action[:encoding])
@@ -13,15 +15,16 @@ class ExecuteLocal < ExecuteBase
13
15
  end
14
16
 
15
17
  def my_execute(cmd, encoding = "UTF-8")
16
- return {exitcode: 0, content: ""} if Project.debug?
18
+ return {exitcode: 0, content: ""} if Project.value[:debug]
17
19
 
18
20
  begin
19
21
  text, status = Open3.capture2e(cmd)
20
22
  exitcode = status.exitstatus
21
23
  rescue => e
22
- verbose Rainbow("!").green
23
24
  text = e.to_s
25
+ log("cmd=<#{cmd}> => #{text}", :error)
24
26
  exitcode = 1
27
+ verbose Rainbow("!").green
25
28
  end
26
29
  content = encode_and_split(encoding, text)
27
30
  {exitcode: exitcode, content: content}
@@ -2,7 +2,6 @@ require "net/ssh"
2
2
  require "net/sftp"
3
3
  require "rainbow"
4
4
  require_relative "../../utils/project"
5
- require_relative "../../utils/verbose"
6
5
  require_relative "execute_base"
7
6
 
8
7
  class ExecuteSSH < ExecuteBase
@@ -1,7 +1,5 @@
1
1
  require "net/telnet"
2
- # require "rainbow"
3
2
  require_relative "../../utils/project"
4
- # require_relative "../../utils/verbose"
5
3
  require_relative "execute_base"
6
4
 
7
5
  class ExecuteTelnet < ExecuteBase