liri 0.2.1 → 0.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.
@@ -15,34 +15,86 @@ module Liri
15
15
  def run(source_code_folder_path, stop = false)
16
16
  return unless valid_project
17
17
 
18
- setup_manager = Liri.set_setup(source_code_folder_path)
18
+ setup_manager = Liri.set_setup(source_code_folder_path, :manager, manager_tests_results_folder_time: DateTime.now.strftime("%d_%m_%y_%H_%M_%S"))
19
19
  manager_folder_path = setup_manager.manager_folder_path
20
+ manager_tests_results_folder_path = setup_manager.manager_tests_results_folder_path
20
21
 
21
- Liri.set_logger(setup_manager.logs_folder_path, 'liri-manager.log')
22
- Liri.logger.info("Proceso Manager iniciado")
23
- Liri.logger.info("Presione Ctrl + c para terminar el proceso Manager manualmente\n", true)
22
+ Liri.set_logger(setup_manager.logs_folder_path, 'lirimanager.log')
23
+ Liri.logger.info('Manager process started', true)
24
+ Liri.logger.info("Press Ctrl + c to finish Manager process manually\n", true)
24
25
 
25
26
  user, password = get_credentials(setup_manager.setup_folder_path)
26
27
  source_code = compress_source_code(source_code_folder_path, manager_folder_path)
27
- manager_data = get_manager_data(user, password, manager_folder_path, source_code)
28
+ manager_data = get_manager_data(user, password, manager_tests_results_folder_path, source_code)
28
29
  all_tests = get_all_tests(source_code)
29
- tests_result = Common::TestsResult.new(manager_folder_path)
30
+ tests_result = Common::TestsResult.new(manager_tests_results_folder_path)
30
31
 
31
- manager = Manager.new(Liri.udp_port, Liri.tcp_port, all_tests, tests_result, manager_folder_path)
32
+ manager = Manager.new(Liri.udp_port, Liri.tcp_port, all_tests, tests_result)
32
33
 
33
34
  threads = []
34
35
  threads << manager.start_client_socket_to_search_agents(manager_data) # Enviar peticiones broadcast a toda la red para encontrar Agents
35
- manager.start_server_socket_to_process_tests(threads[0]) unless stop # Esperar y enviar los test unitarios a los Agents
36
+ unless stop
37
+ # Esperar y enviar los test unitarios a los Agents
38
+ manager.start_server_socket_to_process_tests(threads[0])
39
+ end
36
40
 
37
- Liri.init_exit(stop, threads, 'Manager')
38
- Liri.logger.info("Proceso Manager terminado")
39
- rescue SignalException => e
40
- Liri.logger.info("Exception(#{e}) Proceso Manager terminado manualmente")
41
- Liri.kill(threads)
41
+ Liri.init_exit(stop, threads)
42
+ rescue SignalException
43
+ # Liri.logger.info("\nManager process finished manually", true)
44
+ ensure
45
+ # Siempre se ejecutan estos comandos, haya o no excepción
46
+ Liri.kill(threads) if threads&.any?
47
+ manager&.print_results
48
+ source_code&.delete_compressed_file
49
+ Liri.logger.info("Manager process finished", true)
50
+ end
51
+
52
+ def udp_request_delay
53
+ Liri.setup.manager.udp_request_delay
54
+ end
55
+
56
+ def test_files_by_runner
57
+ Liri.setup.manager.test_files_by_runner
58
+ end
59
+
60
+ def show_share_source_code_progress_bar
61
+ Liri.setup.manager.bar.share_source_code
42
62
  end
43
63
 
44
- def test_samples_by_runner
45
- Liri.setup.test_samples_by_runner
64
+ def print_summary_table
65
+ Liri.setup.manager.print.table.summary
66
+ end
67
+
68
+ def print_detailed_table
69
+ Liri.setup.manager.print.table.detailed
70
+ end
71
+
72
+ def print_summary_failures
73
+ Liri.setup.manager.print.failures.summary
74
+ end
75
+
76
+ def print_detailed_failures
77
+ Liri.setup.manager.print.failures.detailed
78
+ end
79
+
80
+ def show_failed_files_column
81
+ Liri.setup.manager.print.column.failed_files
82
+ end
83
+
84
+ def show_files_load_column
85
+ Liri.setup.manager.print.column.files_load
86
+ end
87
+
88
+ def show_finish_in_column
89
+ Liri.setup.manager.print.column.finish_in
90
+ end
91
+
92
+ def show_batch_run_column
93
+ Liri.setup.manager.print.column.batch_run
94
+ end
95
+
96
+ def show_share_source_code_column
97
+ Liri.setup.manager.print.column.share_source_code
46
98
  end
47
99
 
48
100
  private
@@ -50,9 +102,9 @@ module Liri
50
102
  if File.exist?(File.join(Dir.pwd, 'Gemfile'))
51
103
  true
52
104
  else
53
- Liri.logger.info("No se encuentra un archivo Gemfile por lo que se asume que el directorio actual no corresponde a un proyecto Ruby")
54
- Liri.logger.info("Liri sólo puede ejecutarse en proyectos Ruby")
55
- Liri.logger.info("Proceso Manager terminado")
105
+ Liri.logger.info('Not found Gemfile. Assuming run Manager in not Ruby project')
106
+ Liri.logger.info('Liri can be run only in Ruby projects')
107
+ Liri.logger.info('Manager process finished')
56
108
  false
57
109
  end
58
110
  end
@@ -63,19 +115,24 @@ module Liri
63
115
  end
64
116
 
65
117
  def compress_source_code(source_code_folder_path, manager_folder_path)
66
- source_code = Common::SourceCode.new(source_code_folder_path, manager_folder_path, Liri.compression_class, Liri.unit_test_class)
67
-
68
- Common::Progressbar.start(total: nil, length: 100, format: 'Comprimiendo Código Fuente |%B| %a') do
118
+ source_code = Common::SourceCode.new(source_code_folder_path, manager_folder_path, Liri.ignored_folders_in_compress, Liri.compression_class, Liri.unit_test_class)
119
+ #Common::Progressbar.start(total: nil, length: 120, format: 'Compressing source code |%B| %a') do
120
+ Common::TtyProgressbar.start("Compressing source code |:bar| :percent | Time: :time", total: nil, width: 80, bar_format: :box) do
69
121
  source_code.compress_folder
70
122
  end
71
- puts "\n\n"
72
-
123
+ puts "\n"
124
+ Liri.logger.info("Batch Files: #{test_files_by_runner}", true)
125
+ puts "\n"
73
126
  source_code
127
+ rescue SignalException => e
128
+ # Se captura la excepción sólo para imprimir espacios despues de la barra de progreso
129
+ puts "\n\n"
130
+ raise e
74
131
  end
75
132
 
76
- def get_manager_data(user, password, manager_folder_path, source_code)
133
+ def get_manager_data(user, password, tests_results_folder_path, source_code)
77
134
  Common::ManagerData.new(
78
- folder_path: manager_folder_path,
135
+ tests_results_folder_path: tests_results_folder_path,
79
136
  compressed_file_path: source_code.compressed_file_path,
80
137
  user: user,
81
138
  password: password
@@ -85,39 +142,52 @@ module Liri
85
142
  def get_all_tests(source_code)
86
143
  all_tests = {}
87
144
 
88
- Common::Progressbar.start(total: nil, length: 100, format: 'Extrayendo Pruebas Unitarias |%B| %a') do
145
+ #Common::TtyProgressbar.start("Getting unit tests |:bar| Time::elapsed", total: nil, width: 100) do
146
+ #Common::Progressbar.start(total: nil, length: 120, format: 'Getting unit tests |%B| %a') do
89
147
  all_tests = source_code.all_tests
90
- end
91
- puts "\n\n"
148
+ #end
149
+ #puts "\n\n"
92
150
 
93
151
  all_tests
152
+ rescue SignalException => e
153
+ # Se captura la excepción sólo para imprimir espacios despues de la barra de progreso
154
+ puts "\n\n"
155
+ raise e
94
156
  end
95
157
  end
96
158
 
97
- def initialize(udp_port, tcp_port, all_tests, tests_result, manager_folder_path)
159
+ def initialize(udp_port, tcp_port, all_tests, tests_result)
98
160
  @udp_port = udp_port
99
161
  @udp_socket = UDPSocket.new
100
162
  @tcp_port = tcp_port
101
163
 
102
- @all_tests = all_tests
103
- @all_tests_count = all_tests.size
104
- @all_tests_results = {}
105
- @all_tests_results_count = 0
106
- @all_tests_processing_count = 0
107
- @agents = {}
108
-
109
- @agents_search_processing_enabled = true
110
- @test_processing_enabled = true
111
-
112
- @tests_batch_number = 0
164
+ @batch_num = 0
113
165
  @tests_batches = {}
166
+ @tests_files_count = 0
167
+ build_tests_batches(all_tests)
168
+
169
+ @files_processed = 0
170
+ @agents = {}
171
+ @connected_agents = {}
172
+ @working_agents = {}
114
173
 
115
174
  @tests_result = tests_result
116
175
  @semaphore = Mutex.new
117
176
 
118
- @manager_folder_path = manager_folder_path
177
+ @tests_processing_bar = TTY::ProgressBar::Multi.new("Tests Running Progress")
178
+ @tests_running_progress_bar = @tests_processing_bar.register("Tests files processed :current/:total |:bar| :percent | Time: :time", total: @tests_files_count, width: 80, bar_format: :box)
179
+ @agents_bar = @tests_processing_bar.register("Agents: Connected: :connected, Working: :working")
180
+ @tests_result_bar = @tests_processing_bar.register("Examples: :examples, Passed: :passed, Failures: :failures")
181
+
182
+ @tests_processing_bar.start # Se inicia la multi barra de progreso
183
+
184
+ # Se establece el estado inicial de las barras
185
+ @tests_running_progress_bar.use(Common::TtyProgressbar::TimeFormatter) # Se configura el uso de un nuevo token llamado time para mostrar el tiempo de ejcución
186
+ @tests_running_progress_bar.advance(0) # Esto obliga a que esta barra se muestre antes que los siguientes
187
+ @tests_running_progress_bar.pause
119
188
 
120
- @progressbar = ProgressBar.create(starting_at: 0, total: @all_tests_count, length: 100, format: 'Progress %c/%C |%b=%i| %p%% | %a')
189
+ @agents_bar.advance(0, connected: "0", working: "0")
190
+ @tests_result_bar.advance(0, examples: "0", passed: "0", failures: "0")
121
191
  end
122
192
 
123
193
  # Inicia un cliente udp que hace un broadcast en toda la red para iniciar una conexión con los Agent que estén escuchando
@@ -125,14 +195,12 @@ module Liri
125
195
  # El cliente udp se ejecuta en bucle dentro de un hilo, esto permite realizar otras tareas mientras este hilo sigue sondeando
126
196
  # la red para obtener mas Agents. Una vez que los tests terminan de ejecutarse, este hilo será finalizado.
127
197
  Thread.new do
128
- Liri.logger.info("Buscando Agentes... Espere")
129
- Liri.logger.info("Se emite un broadcast cada #{Liri.udp_request_delay} segundos en el puerto UDP: #{@udp_port}
130
- (Se mantiene escaneando la red para encontrar Agents)
131
- ")
132
- while agents_search_processing_enabled
198
+ Liri.logger.info('Searching agents... Wait')
199
+ Liri.logger.info("Sending UDP broadcast each #{Manager.udp_request_delay} seconds in UDP port: #{@udp_port}")
200
+ while processing
133
201
  @udp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
134
202
  @udp_socket.send(manager_data.to_h.to_json, 0, '<broadcast>', @udp_port)
135
- sleep(Liri.udp_request_delay) # Se pausa un momento antes de efectuar nuevamente la petición broadcast
203
+ sleep(Manager.udp_request_delay) # Se pausa un momento antes de efectuar nuevamente la petición broadcast
136
204
  end
137
205
  end
138
206
  end
@@ -142,179 +210,417 @@ module Liri
142
210
  begin
143
211
  tcp_socket = TCPServer.new(@tcp_port) # se hace un bind al puerto dado
144
212
  rescue Errno::EADDRINUSE => e
145
- Liri.logger.error("Exception(#{e}) Puerto TCP #{@tcp_port} ocupado.")
213
+ Liri.logger.error("Exception(#{e}) Busy UDP port #{@tcp_port}.")
146
214
  Thread.kill(search_agents_thread)
147
- Thread.exit
215
+ return
148
216
  end
149
217
 
150
- Liri.logger.info("En espera para establecer conexión con los Agents en el puerto TCP: #{@tcp_port}
151
- (Se espera que algún Agent se conecte para ejecutar las pruebas como respuesta al broadcast UDP)
152
- ")
218
+ Liri.logger.info("Waiting Agents connection in TCP port: #{@tcp_port}")
153
219
  # El siguiente bucle permite que varios clientes es decir Agents se conecten
154
220
  # De: http://www.w3big.com/es/ruby/ruby-socket-programming.html
155
- while test_processing_enabled
221
+ while processing
156
222
  Thread.start(tcp_socket.accept) do |client|
157
223
  agent_ip_address = client.remote_address.ip_address
158
- response = client.recvfrom(1000).first
159
-
160
- Liri.logger.info("\nConexión iniciada con el Agente: #{agent_ip_address}")
161
- Liri.logger.info("Respuesta al broadcast recibida del Agent: #{agent_ip_address} en el puerto TCP: #{@tcp_port}: #{response}")
162
-
163
- # Se le indica al agente que proceda
164
- client.puts({ msg: 'Recibido', exist_tests: all_tests.any? }.to_json)
165
-
166
- if all_tests.empty?
167
- # No importa lo que le haga, el broadcast udp no se muere al instante y el agente sigue respondiendo
168
- # Las siguientes dos lineas son para que se deje de hacer el broadcast pero aun asi se llegan a hacer
169
- # 3 a 4 broadcast antes de que se finalize el proceso, al parecer el broadcast va a tener que quedar asi
170
- # y mejorar el codigo para que se envien test pendientes para eso hay que llevar una lista de test pendientes
171
- # tests enviados sin resultados, tests finalizados, si se recibe respuesta al broadcast se trata de enviar primero test pendientes
172
- # luego test enviados sin resultados o sino ignorar
173
- Thread.kill(search_agents_thread)
174
- agents_search_processing_enabled = false
175
- Liri.logger.info("Se termina cualquier proceso pendiente con el Agent #{agent_ip_address} en el puerto TCP: #{@tcp_port}: #{response}")
176
- client.close
177
- Thread.exit
178
- end
224
+ hardware_specs = nil
225
+ run_tests_batch_time_start = nil
226
+ share_source_code_time_start = nil
227
+ share_source_code_progress_bar = nil
228
+
229
+ while line = client.gets
230
+ client_data = JSON.parse(line.chop)
231
+ msg = client_data['msg']
232
+
233
+ if msg == 'get_source_code'
234
+ if registered_agent?(agent_ip_address)
235
+ client.puts({ msg: 'already_connected' }.to_json)
236
+ client.close
237
+ break
238
+ else
239
+ register_agent(agent_ip_address)
240
+ update_connected_agents(agent_ip_address)
241
+ hardware_specs = client_data['hardware_specs']
242
+ msg = processing ? 'proceed_get_source_code' : 'no_exist_tests'
243
+ share_source_code_time_start = Time.now
244
+
245
+ share_source_code_progress_bar = start_share_source_code_progress_bar(hardware_specs, msg)
179
246
 
180
- while all_tests.any?
181
- time_in_seconds = Liri::Common::Benchmarking.start(start_msg: "Proceso de Ejecución de pruebas. Agent: #{agent_ip_address}. Espere... ", end_msg: "Proceso de Ejecución de pruebas. Agent: #{agent_ip_address}. Duración: ", stdout: false) do
182
- tests_batch = tests_batch(agent_ip_address)
183
- break unless tests_batch
247
+ client.puts({ msg: msg }.to_json)
248
+ end
249
+ end
184
250
 
185
- begin
186
- Liri.logger.debug("Conjunto de pruebas enviadas al Agent #{agent_ip_address}: #{tests_batch}")
251
+ if msg == 'get_source_code_fail'
252
+ stop_share_source_code_progress_bar(hardware_specs, share_source_code_progress_bar)
187
253
 
188
- client.puts(tests_batch.to_json) # Se envia el lote de tests
189
- response = client.recvfrom(1000).first # Se recibe la respuesta. Cuando mas alto es el parámetro de recvfrom, mas datos se reciben osino se truncan.
190
- rescue Errno::EPIPE => e
191
- # Esto al parecer se da cuando el Agent ya cerró las conexiones y el Manager intenta contactar
192
- Liri.logger.error("Exception(#{e}) El Agent #{agent_ip_address} ya terminó la conexión")
193
- # Si el Agente ya no responde es mejor romper el bucle para que no quede colgado
254
+ client.puts({ msg: 'finish_agent' }.to_json)
255
+ client.close
256
+ break
257
+ end
258
+
259
+ # Primera ejecucion de pruebas
260
+ if msg == 'get_tests_files'
261
+ stop_share_source_code_progress_bar(hardware_specs, share_source_code_progress_bar)
262
+
263
+ share_source_code_time_end = Time.now - share_source_code_time_start
264
+
265
+ Liri.logger.info("Running unit tests. Agent: #{agent_ip_address}. Wait... ", false)
266
+
267
+ start_tests_running_progress_bar
268
+ run_tests_batch_time_start = Time.now
269
+ update_working_agents(agent_ip_address)
270
+ tests_batch = tests_batch(agent_ip_address, hardware_specs, share_source_code_time_end)
271
+
272
+ if tests_batch.empty?
273
+ client.puts({ msg: 'no_exist_tests' }.to_json)
274
+ client.close
194
275
  break
276
+ else
277
+ client.puts(tests_batch.to_json) # Se envia el lote de tests
195
278
  end
196
279
  end
197
280
 
198
- # Se captura por si acaso los errores de parseo JSON
199
- begin
200
- tests_result = JSON.parse(response)
201
- Liri.logger.debug("Respuesta del Agent #{agent_ip_address}: #{tests_result}")
202
- process_tests_result(agent_ip_address, tests_result, time_in_seconds)
203
- rescue JSON::ParserError => e
204
- Liri.logger.error("Exception(#{e}) Error de parseo JSON")
281
+ # Segunda ejecucion de pruebas y las siguientes ejecuciones
282
+ if msg == 'processed_tests'
283
+ tests_result = client_data
284
+ Liri.logger.debug("Agent response #{agent_ip_address}: #{tests_result}")
285
+ process_tests_result(agent_ip_address, hardware_specs, tests_result, run_tests_batch_time_start)
286
+
287
+ run_tests_batch_time_start = Time.now
288
+
289
+ tests_batch = tests_batch(agent_ip_address, hardware_specs, 0)
290
+ if tests_batch.empty?
291
+ client.puts({ msg: 'no_exist_tests' }.to_json)
292
+ client.close
293
+ break
294
+ else
295
+ client.puts(tests_batch.to_json) # Se envia el lote de tests
296
+ end
205
297
  end
206
298
  end
207
299
 
208
- update_processing_statuses
209
- Liri.logger.info("Se termina la conexión con el Agent #{agent_ip_address} en el puerto TCP: #{@tcp_port}")
210
- begin
211
- client.puts('exit') # Se envía el string exit para que el Agent sepa que el proceso terminó
212
- client.close # se desconecta el cliente
213
- rescue Errno::EPIPE => e
214
- # Esto al parecer se da cuando el Agent ya cerró las conexiones y el Manager intenta contactar
215
- Liri.logger.error("Exception(#{e}) El Agent #{agent_ip_address} ya terminó la conexión")
216
- # Si el Agente ya no responde es mejor terminar el hilo. Aunque igual quedará colgado el Manager
217
- # mientras sigan pruebas pendientes
218
- Thread.exit
219
- end
300
+ Thread.kill(search_agents_thread)
301
+ rescue Errno::EPIPE => e
302
+ # Esto al parecer se da cuando el Agent ya cerró las conexiones y el Manager intenta contactar
303
+ Liri.logger.error("Exception(#{e}) Agent #{agent_ip_address} already finished connection")
304
+ # Si el Agente ya no responde es mejor terminar el hilo. Aunque igual quedará colgado el Manager
305
+ # mientras sigan pruebas pendientes
306
+ Thread.exit
220
307
  end
221
308
  end
222
-
223
- Liri.clean_folder_content(@manager_folder_path)
224
- @tests_result.print_summary
225
- print_agents_summary
226
- @tests_result.print_failures if Liri.print_failures
227
309
  end
228
310
 
229
- def all_tests
311
+ def processing
230
312
  @semaphore.synchronize do
231
- @all_tests
313
+ @unfinished_tests_batches.positive?
232
314
  end
233
315
  end
234
316
 
235
- def agents_search_processing_enabled=(value)
236
- @semaphore.synchronize do
237
- @agents_search_processing_enabled = value
317
+ def build_tests_batches(all_tests)
318
+ while all_tests.any?
319
+ @batch_num += 1 # Se numera cada lote
320
+ samples = all_tests.sample!(Manager.test_files_by_runner) # Se obtiene algunos tests
321
+ samples_keys = samples.keys # Se obtiene la clave asignada a los tests
322
+ files_count = samples.size
323
+ status = "pending"
324
+ @tests_files_count += files_count
325
+ # Se construye el lote a enviar
326
+ tests_batch = {
327
+ batch_num: @batch_num,
328
+ tests_batch_keys: samples_keys,
329
+ msg: "process_tests",
330
+ files_count: files_count,
331
+ status: status,
332
+ files_status: "#{files_count} #{status}",
333
+ agent_ip_address: "",
334
+ examples: 0,
335
+ passed: 0,
336
+ failures: 0,
337
+ pending: 0,
338
+ failed_files: "",
339
+ files_load: 0,
340
+ finish_in: 0,
341
+ batch_run: 0,
342
+ share_source_code: 0,
343
+ tests_runtime: 0,
344
+ hardware_specs: ""
345
+ }
346
+ @tests_batches[@batch_num] = tests_batch
238
347
  end
348
+
349
+ @unfinished_tests_batches = @batch_num
239
350
  end
240
351
 
241
- def agents_search_processing_enabled
352
+ def tests_batch(agent_ip_address, hardware_specs, share_source_code_time_end)
353
+ # Se inicia un semáforo para evitar que varios hilos actualicen variables compartidas
242
354
  @semaphore.synchronize do
243
- @agents_search_processing_enabled
355
+ return {} if @unfinished_tests_batches.zero?
356
+
357
+ tests_batch = {}
358
+ pending_tests_batch = {}
359
+ sent_tests_batch = {}
360
+
361
+ @tests_batches.each_value do |batch|
362
+ if batch[:status] == "pending"
363
+ pending_tests_batch = batch
364
+ break
365
+ elsif batch[:status] == "sent"
366
+ sent_tests_batch = batch # Es importante que este no tenga un break para guardar el ultimo enviado
367
+ # el cual tiene menos probabilidades de terminar de ejecutarse rapido
368
+ end
369
+ end
370
+
371
+ # Es importante setear el status y el hardware_spec solo si los hashes no estan vacios
372
+ # Porque si estan vacios significa que ya no hay tests que ejecutar, y si seteamos algun valor en el hash
373
+ # estando este vacio entonces se tratara de ejecutar algo sin los datos suficientes y fallara
374
+ if pending_tests_batch.any?
375
+ tests_batch = pending_tests_batch
376
+ tests_batch[:status] = "sent"
377
+ tests_batch[:agent_ip_address] = agent_ip_address
378
+ tests_batch[:hardware_specs] = hardware_specs
379
+ elsif sent_tests_batch.any?
380
+ tests_batch = sent_tests_batch
381
+ tests_batch[:status] = "resent"
382
+ tests_batch[:agent_ip_address] = agent_ip_address
383
+ tests_batch[:hardware_specs] = hardware_specs
384
+ end
385
+
386
+ return {} if tests_batch.empty?
387
+
388
+ tests_batch[:agent_ip_address] = agent_ip_address
389
+ tests_batch[:share_source_code] = share_source_code_time_end
390
+
391
+ Liri.logger.debug("Tests batches sent to Agent #{agent_ip_address}: #{tests_batch}")
392
+ # se devuelve el hash con los datos que se enviarán al agente, por eso, primero se remueven los datos innecesarios
393
+ tests_batch.remove(:files_count, :status, :files_status, :agent_ip_address, :examples, :passed, :failures,
394
+ :pending, :failed_files, :files_load, :finish_in, :batch_run, :share_source_code,
395
+ :tests_runtime, :hardware_specs)
244
396
  end
245
397
  end
246
398
 
247
- def test_processing_enabled
399
+ def process_tests_result(agent_ip_address, hardware_specs, tests_result, run_tests_batch_time_start)
400
+ # Se inicia un semáforo para evitar que varios hilos actualicen variables compartidas
248
401
  @semaphore.synchronize do
249
- @test_processing_enabled
402
+ batch_num = tests_result['batch_num']
403
+ tests_result_file_name = tests_result['tests_result_file_name']
404
+ status = "processed"
405
+ # Sólo se procesan las pruebas en estado sent o resent, caso contrario no se avanza con el procesamiento
406
+ return if (["pending", status]).include?(@tests_batches[batch_num][:status])
407
+
408
+ tests_result = @tests_result.process(tests_result_file_name)
409
+ return if tests_result.empty?
410
+
411
+ @unfinished_tests_batches -= 1
412
+
413
+ files_count = @tests_batches[batch_num][:files_count]
414
+ @files_processed += files_count
415
+
416
+ batch_runtime = Time.now - run_tests_batch_time_start
417
+
418
+ @tests_running_progress_bar.advance(files_count)
419
+ @tests_result_bar.advance(1, examples: @tests_result.examples.to_s, passed: @tests_result.passed.to_s, failures: @tests_result.failures.to_s)
420
+ @tests_running_progress_bar.stop if @unfinished_tests_batches.zero?
421
+
422
+ @tests_batches[batch_num][:status] = status
423
+ @tests_batches[batch_num][:files_status] = "#{files_count} #{status}"
424
+ @tests_batches[batch_num][:agent_ip_address] = agent_ip_address
425
+ @tests_batches[batch_num][:examples] = tests_result[:examples]
426
+ @tests_batches[batch_num][:passed] = tests_result[:passed]
427
+ @tests_batches[batch_num][:failures] = tests_result[:failures]
428
+ @tests_batches[batch_num][:pending] = tests_result[:pending]
429
+ @tests_batches[batch_num][:failed_files] = tests_result[:failed_files]
430
+ @tests_batches[batch_num][:files_load] = tests_result[:files_load]
431
+ @tests_batches[batch_num][:finish_in] = tests_result[:finish_in]
432
+ @tests_batches[batch_num][:batch_run] = batch_runtime
433
+ @tests_batches[batch_num][:tests_runtime] = @tests_batches[batch_num][:batch_run] + @tests_batches[batch_num][:share_source_code]
434
+ @tests_batches[batch_num][:hardware_specs] = hardware_specs
435
+
436
+ Liri.logger.info("Processed unit tests by Agent: #{agent_ip_address}: #{files_count}")
250
437
  end
251
438
  end
252
439
 
253
- def update_processing_statuses
254
- @semaphore.synchronize do
255
- @test_processing_enabled = false if @all_tests_count == @all_tests_results_count
256
- @agents_search_processing_enabled = false if @all_tests_count == @all_tests_processing_count
440
+ def print_results
441
+ @tests_processing_bar&.stop
442
+ print_summary_table if Manager.print_summary_table
443
+ print_detailed_table if Manager.print_detailed_table
444
+ @tests_result.print_summary_failures if Manager.print_summary_failures
445
+ @tests_result.print_detailed_failures if Manager.print_detailed_failures
446
+ end
447
+
448
+ def print_summary_table
449
+ processed_tests_batches_by_agent = processed_tests_batches_by_agents
450
+ rows = processed_tests_batches_by_agent.values.map do |value|
451
+ value[:files_load] = to_duration(value[:files_load]) if value[:files_load]
452
+ value[:finish_in] = to_duration(value[:finish_in]) if value[:finish_in]
453
+ value[:batch_run] = to_duration(value[:batch_run]) if value[:batch_run]
454
+ value[:share_source_code] = to_duration(value[:share_source_code]) if value[:share_source_code]
455
+ value[:tests_runtime] = to_duration(value[:tests_runtime]) if value[:tests_runtime]
456
+ value.values
257
457
  end
458
+
459
+ rows << Array.new(rows.size) # Se agrega una linea vacia antes de mostrar los totales
460
+ rows << summary_footer.remove(:batch_num).values
461
+ header = processed_tests_batches_by_agent.values.first.keys
462
+
463
+ table = Terminal::Table.new title: 'Summary', headings: header, rows: rows
464
+ table.style = { padding_left: 3, border_x: '=', border_i: 'x'}
465
+
466
+ Liri.logger.info("\n#{table}", true)
258
467
  end
259
468
 
260
- def tests_batch(agent_ip_address)
261
- # Se inicia un semáforo para evitar que varios hilos actualicen variables compartidas
262
- @semaphore.synchronize do
263
- return nil if @all_tests.empty?
469
+ def processed_tests_batches_by_agents
470
+ tests_batches = {}
471
+ files_count = {}
472
+ @tests_batches.each_value do |processed_test_batch|
473
+ agent_ip_address = processed_test_batch[:agent_ip_address]
474
+ status = processed_test_batch[:status]
475
+ key = "#{agent_ip_address}#{status}"
476
+ if tests_batches[key]
477
+ files_count[key] += processed_test_batch[:files_count]
478
+ tests_batches[key][:files_status] = "#{files_count[key]} #{status}"
479
+ tests_batches[key][:examples] += processed_test_batch[:examples]
480
+ tests_batches[key][:passed] += processed_test_batch[:passed]
481
+ tests_batches[key][:failures] += processed_test_batch[:failures]
482
+ tests_batches[key][:failed_files] += processed_test_batch[:failed_files] if Manager.show_failed_files_column
483
+ tests_batches[key][:files_load] += processed_test_batch[:files_load] if Manager.show_files_load_column
484
+ tests_batches[key][:finish_in] += processed_test_batch[:finish_in] if Manager.show_finish_in_column
485
+ tests_batches[key][:batch_run] += processed_test_batch[:batch_run] if Manager.show_batch_run_column
486
+ tests_batches[key][:share_source_code] += processed_test_batch[:share_source_code] if Manager.show_share_source_code_column
487
+ tests_batches[key][:tests_runtime] += processed_test_batch[:tests_runtime]
488
+ else
489
+ files_count[key] = processed_test_batch[:files_count]
264
490
 
265
- @tests_batch_number += 1 # Se numera cada lote
266
- samples = @all_tests.sample!(Manager.test_samples_by_runner) # Se obtiene algunos tests
267
- samples_keys = samples.keys # Se obtiene la clave asignada a los tests
268
- @all_tests_processing_count += samples_keys.size
491
+ _processed_test_batch = processed_test_batch.clone # Clone to change values in other hash
492
+ _processed_test_batch.remove!(:batch_num, :tests_batch_keys, :msg, :files_count, :status, :agent_ip_address,
493
+ :pending)
269
494
 
270
- @agents[agent_ip_address] = { agent_ip_address: agent_ip_address, tests_processed_count: 0, examples: 0, failures: 0, time_in_seconds: 0, duration: '' } unless @agents[agent_ip_address]
495
+ _processed_test_batch.remove!(:failed_files) unless Manager.show_failed_files_column
496
+ _processed_test_batch.remove!(:files_load) unless Manager.show_files_load_column
497
+ _processed_test_batch.remove!(:finish_in) unless Manager.show_finish_in_column
498
+ _processed_test_batch.remove!(:batch_run) unless Manager.show_batch_run_column
499
+ _processed_test_batch.remove!(:share_source_code) unless Manager.show_share_source_code_column
500
+ _processed_test_batch[:files_status] = "#{files_count[key]} #{status}"
271
501
 
272
- #@tests_batches[@tests_batch_number] = { agent_ip_address: agent_ip_address, tests_batch_keys: samples_keys } # Se guarda el lote a enviar
273
- tests_batch = { tests_batch_number: @tests_batch_number, tests_batch_keys: samples_keys } # Se construye el lote a enviar
274
- tests_batch
502
+ tests_batches[key] = _processed_test_batch
503
+ end
275
504
  end
505
+ tests_batches
276
506
  end
277
507
 
278
- def process_tests_result(agent_ip_address, tests_result, time_in_seconds)
279
- # Se inicia un semáforo para evitar que varios hilos actualicen variables compartidas
280
- @semaphore.synchronize do
281
- tests_batch_number = tests_result['tests_batch_number']
282
- tests_result_file_name = tests_result['tests_result_file_name']
283
- tests_batch_keys_size = tests_result['tests_batch_keys_size']
508
+ def print_detailed_table
509
+ rows = @tests_batches.values.map do |value|
510
+ value.remove!(:tests_batch_keys, :msg, :files_count, :status, :agent_ip_address, :pending)
511
+
512
+ value.remove!(:failed_files) unless Manager.show_failed_files_column
513
+ value.remove!(:files_load) unless Manager.show_files_load_column
514
+ value.remove!(:finish_in) unless Manager.show_finish_in_column
515
+ value.remove!(:batch_run) unless Manager.show_batch_run_column
516
+ value.remove!(:share_source_code) unless Manager.show_share_source_code_column
517
+
518
+ value[:files_load] = to_duration(value[:files_load]) if value[:files_load]
519
+ value[:finish_in] = to_duration(value[:finish_in]) if value[:finish_in]
520
+ value[:batch_run] = to_duration(value[:batch_run]) if value[:batch_run]
521
+ value[:share_source_code] = to_duration(value[:share_source_code]) if value[:share_source_code]
522
+ value[:tests_runtime] = to_duration(value[:tests_runtime])
523
+ value.values
524
+ end
284
525
 
285
- #tests_batch_keys = @tests_batches[tests_batch_number][:tests_batch_keys]
286
- tests_processed_count = tests_batch_keys_size
287
- @all_tests_results_count += tests_processed_count
526
+ rows << Array.new(rows.size) # Se agrega una linea vacia antes de mostrar los totales
527
+ rows << summary_footer.values
528
+ header = @tests_batches.values.first.keys
288
529
 
289
- @progressbar.progress = @all_tests_results_count
530
+ table = Terminal::Table.new title: 'Detailed Summary', headings: header, rows: rows
531
+ table.style = { padding_left: 3, border_x: '=', border_i: 'x' }
290
532
 
291
- #@tests_batches[tests_batch_number][:tests_result_file_name] = tests_result_file_name
533
+ Liri.logger.info("\n#{table}", true)
534
+ end
292
535
 
293
- tests_result = @tests_result.process(tests_result_file_name)
536
+ def summary_footer
537
+ hash = {}
538
+ hash[:batch_num] = ""
539
+ hash[:files_status] = "#{@tests_files_count} in total"
540
+ hash[:examples] = @tests_result.examples
541
+ hash[:passed] = @tests_result.passed
542
+ hash[:failures] = @tests_result.failures
543
+ hash[:failed_files] = "" if Manager.show_failed_files_column
544
+ hash[:files_load] = "" if Manager.show_files_load_column
545
+ hash[:finish_in] = "" if Manager.show_finish_in_column
546
+ hash[:batch_run] = "" if Manager.show_batch_run_column
547
+ hash[:share_source_code] = "" if Manager.show_share_source_code_column
548
+ hash[:tests_runtime] = ""
549
+ hash[:hardware_specs] = ""
550
+ hash
551
+ end
552
+
553
+ def registered_agent?(agent_ip_address)
554
+ @agents[agent_ip_address]
555
+ end
556
+
557
+ def register_agent(agent_ip_address)
558
+ @agents[agent_ip_address] = agent_ip_address
559
+ Liri.logger.info("\nStarted connection with Agent: #{agent_ip_address} in TCP port: #{@tcp_port}")
560
+ end
561
+
562
+ def update_connected_agents(agent_ip_address)
563
+ unless @connected_agents[agent_ip_address]
564
+ @connected_agents[agent_ip_address] = agent_ip_address
565
+ update_agents_bar
566
+ end
567
+ end
294
568
 
295
- @agents[agent_ip_address][:tests_processed_count] += tests_processed_count
296
- @agents[agent_ip_address][:examples] += tests_result[:example_quantity]
297
- @agents[agent_ip_address][:failures] += tests_result[:failure_quantity]
298
- @agents[agent_ip_address][:time_in_seconds] += time_in_seconds
299
- @agents[agent_ip_address][:duration] = @agents[agent_ip_address][:time_in_seconds].to_duration
569
+ def update_working_agents(agent_ip_address)
570
+ unless @working_agents[agent_ip_address]
571
+ @working_agents[agent_ip_address] = agent_ip_address
572
+ update_agents_bar
573
+ end
574
+ end
300
575
 
301
- Liri.logger.info("Pruebas procesadas por Agente: #{agent_ip_address}: #{@agents[agent_ip_address][:tests_processed_count]}")
576
+ def update_agents_bar
577
+ @agents_bar.advance(1, connected: @connected_agents.size.to_s, working: @working_agents.size.to_s)
578
+ end
579
+
580
+ def start_share_source_code_progress_bar(hardware_specs, msg)
581
+ if msg == 'proceed_get_source_code' && Manager.show_share_source_code_progress_bar
582
+ share_source_code_progress_bar = @tests_processing_bar.register("Sharing source code |:bar| :percent | Time: :time | Agent: [:agent ]", total: nil, width: 20, bar_format: :box)
583
+ share_source_code_progress_bar.start
584
+ share_source_code_progress_bar.use(Common::TtyProgressbar::TimeFormatter)
585
+ Thread.new do
586
+ animation_count = 0
587
+ while !share_source_code_progress_bar.stopped?
588
+ share_source_code_progress_bar.advance(1, agent: hardware_specs)
589
+
590
+ share_source_code_progress_bar.update(unknown: Common::TtyProgressbar::ANIMATION2[animation_count])
591
+ animation_count += 1
592
+ animation_count = 0 if animation_count == 3
593
+
594
+ sleep(0.1)
595
+ end
596
+ end
302
597
  end
598
+ share_source_code_progress_bar
303
599
  end
304
600
 
305
- def print_agents_summary
306
- rows = @agents.values.map { |line| line.values }
307
- headings = @agents.values.first.keys
601
+ def stop_share_source_code_progress_bar(hardware_specs, share_source_code_progress_bar)
602
+ if Manager.show_share_source_code_progress_bar
603
+ share_source_code_progress_bar.update(total: 1, agent: hardware_specs)
604
+ share_source_code_progress_bar.stop
605
+ end
606
+ end
308
607
 
309
- table = Terminal::Table.new title: "Resúmen", headings: headings, rows: rows
310
- table.style = {padding_left: 3, border_x: "=", border_i: "x" }
311
- table.align_column(1, :right)
312
- table.align_column(2, :right)
313
- table.align_column(3, :right)
314
- table.align_column(4, :right)
315
- table.align_column(5, :right)
608
+ def start_tests_running_progress_bar
609
+ @semaphore.synchronize do
610
+ # Es importante hacer un reset acá osino va a contar desde que se instancia y no desde que se inicia la ejecución
611
+ # del primer test. Solo se resetea si esta paused para evitar que al conectarse con cada Agent se vuelva a resetear
612
+ @tests_running_progress_bar.reset if @tests_running_progress_bar.paused?
613
+ Thread.new do
614
+ while !@tests_running_progress_bar.stopped?
615
+ @tests_running_progress_bar.advance(0)
616
+ sleep(0.1) # Es importante que las otras barras tambien tengan el mismo sleep para que sean mas consistentes en sus resultados
617
+ end
618
+ end
619
+ end
620
+ end
316
621
 
317
- puts table
622
+ def to_duration(value)
623
+ Common::Duration.humanize(value, times_round: Liri.times_round, times_round_type: Liri.times_round_type)
318
624
  end
319
625
  end
320
626
  end