holado 0.4.2__py3-none-any.whl → 0.5.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of holado might be problematic. Click here for more details.

Files changed (57) hide show
  1. holado/common/context/session_context.py +6 -0
  2. {holado-0.4.2.dist-info → holado-0.5.2.dist-info}/METADATA +1 -1
  3. {holado-0.4.2.dist-info → holado-0.5.2.dist-info}/RECORD +57 -28
  4. holado_ais/ais/ais_messages.py +44 -15
  5. holado_ais/ais/patch_pyais.py +47 -176
  6. holado_ais/tests/behave/steps/ais/ais_messages_steps.py +2 -6
  7. holado_core/common/resource/persisted_data_manager.py +4 -5
  8. holado_core/common/resource/resource_manager.py +4 -18
  9. holado_core/common/tools/converters/converter.py +14 -3
  10. holado_core/common/tools/tools.py +9 -2
  11. holado_data/data/generator/generator_manager.py +1 -13
  12. holado_db/tools/db/clients/base/db_audit.py +94 -0
  13. holado_db/tools/db/clients/base/db_client.py +145 -59
  14. holado_db/tools/db/clients/postgresql/postgresql_audit.py +75 -0
  15. holado_db/tools/db/clients/postgresql/postgresql_client.py +4 -0
  16. holado_db/tools/db/clients/sqlite/sqlite_audit.py +70 -0
  17. holado_db/tools/db/clients/sqlite/sqlite_client.py +4 -0
  18. holado_django/server/patch_djangogrpcframework.py +46 -0
  19. holado_logging/common/logging/holado_logger.py +1 -6
  20. holado_multitask/multithreading/thread.py +13 -7
  21. holado_multitask/multithreading/timer.py +3 -0
  22. holado_python/common/tools/datetime.py +31 -13
  23. holado_python/standard_library/ssl/resources/certificates/tcpbin.crt +16 -16
  24. holado_python/standard_library/ssl/resources/certificates/tcpbin.key +26 -26
  25. holado_rabbitmq/tools/rabbitmq/rabbitmq_blocking_client.py +24 -2
  26. holado_rabbitmq/tools/rabbitmq/rabbitmq_client.py +2 -2
  27. holado_report/report/builders/failure_report_builder.py +129 -0
  28. holado_report/report/report_manager.py +4 -0
  29. holado_rest/api/rest/rest_client.py +19 -7
  30. holado_rest/tests/behave/steps/api/rest_client_steps.py +1 -2
  31. test_holado/features/NonReg/holado_ais/message_types/type-10.feature +38 -0
  32. test_holado/features/NonReg/holado_ais/message_types/type-12.feature +37 -0
  33. test_holado/features/NonReg/holado_ais/message_types/type-14.feature +36 -0
  34. test_holado/features/NonReg/holado_ais/message_types/type-15.feature +36 -0
  35. test_holado/features/NonReg/holado_ais/message_types/type-16.feature +38 -0
  36. test_holado/features/NonReg/holado_ais/message_types/type-17.feature +46 -0
  37. test_holado/features/NonReg/holado_ais/message_types/type-18.feature +37 -0
  38. test_holado/features/NonReg/holado_ais/message_types/type-19.feature +38 -0
  39. test_holado/features/NonReg/holado_ais/message_types/type-1_2_3.feature +42 -0
  40. test_holado/features/NonReg/holado_ais/message_types/type-20.feature +38 -0
  41. test_holado/features/NonReg/holado_ais/message_types/type-21.feature +37 -0
  42. test_holado/features/NonReg/holado_ais/message_types/type-22.feature +84 -0
  43. test_holado/features/NonReg/holado_ais/message_types/type-23.feature +49 -0
  44. test_holado/features/NonReg/holado_ais/message_types/type-24.feature +72 -0
  45. test_holado/features/NonReg/holado_ais/message_types/type-25.feature +143 -0
  46. test_holado/features/NonReg/holado_ais/message_types/type-26.feature +144 -0
  47. test_holado/features/NonReg/holado_ais/message_types/type-27.feature +36 -0
  48. test_holado/features/NonReg/holado_ais/message_types/type-4_11.feature +39 -0
  49. test_holado/features/NonReg/holado_ais/message_types/type-5.feature +33 -0
  50. test_holado/features/NonReg/holado_ais/message_types/type-6.feature +37 -0
  51. test_holado/features/NonReg/holado_ais/message_types/type-7_13.feature +43 -0
  52. test_holado/features/NonReg/holado_ais/message_types/type-8.feature +37 -0
  53. test_holado/features/NonReg/holado_ais/message_types/type-9.feature +37 -0
  54. test_holado/tools/django/api_grpc/manage.py +2 -0
  55. test_holado/tools/django/api_grpc/patch_djangogrpcframework.py +42 -0
  56. {holado-0.4.2.dist-info → holado-0.5.2.dist-info}/WHEEL +0 -0
  57. {holado-0.4.2.dist-info → holado-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -2,20 +2,20 @@
2
2
  MIIDZTCCAk2gAwIBAgIBKjANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMCVVMx
3
3
  CzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQKDAZ0
4
4
  Y3BiaW4xDDAKBgNVBAsMA29wczETMBEGA1UEAwwKdGNwYmluLmNvbTEjMCEGCSqG
5
- SIb3DQEJARYUaGFycnliYWdkaUBnbWFpbC5jb20wHhcNMjUwNzAxMTQyMDI3WhcN
6
- MjUwNzAyMTQyMDI3WjAcMRowGAYDVQQDDBF0Y3BiaW4uY29tLWNsaWVudDCCASIw
7
- DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALmLkz54fCoSSM/uG945GvhavQF8
8
- unsEDd0RuHWZPIBcrlEzhLLiiiegET4UFlywF4wSfbiNGgwr3Uw+6ph3xLrNs91B
9
- EYqOGesT9wRPMlon88qsaVIFfbqfg4e1m0si+/NwZMk+5bBeYC0blXk7HiTZ5KJt
10
- QNLeduIYaPSuyq+qxl/xDqWOifbKcgycitgCPKPxohtwa5h6IjU3WYb7+xNPt3cx
11
- +hoCwfab1NgVBse6ZsUf81nkCEVKKLaNvlDkmXp9ItqZZay+0/yzLCgNyf8gDprU
12
- 8u+nDM+pcz83OtIQWVeN5MtFjkA4nik55yqdlqnB1OWzEzaMSMh6Vsy13xMCAwEA
13
- AaNCMEAwHQYDVR0OBBYEFG5H5JaQhWehs8VRJu88HbHgK9CvMB8GA1UdIwQYMBaA
14
- FOLuMowBSAZfV5v82LmlaIIOvU/DMA0GCSqGSIb3DQEBCwUAA4IBAQCs93UG22r0
15
- ZIF10IAoXVbPWOeXpKJgWpsQYURsGw7sB+Cgr/vK3P1lkZVMl72LUzNazmOQldev
16
- zUQ0TtOq24go1iAYWc7zYlxo9OHNkbAUDoViKP63K/CkXNCIkXg00vmEUco1PQWU
17
- +OEaTNrQCeiW2eqYie9WNc4T5a7aUHT3Xno7G8KXTM7gtW35FNI/b0CwVWHRFzev
18
- n9VhRI9xrNYxpDqqxYu7diIV4TQVmscmNicuEtDB98kJ7yOAhlpzQhIFi1vQEefD
19
- 4gZZ3ihlLd2XJWxiXZgeiz7dzva4oGcr9mU5Uohza3u/sjF34kx02+gNuv8x4Wil
20
- CU9Rl0WFGJ00
5
+ SIb3DQEJARYUaGFycnliYWdkaUBnbWFpbC5jb20wHhcNMjUwNzI4MTUyNDU5WhcN
6
+ MjUwNzI5MTUyNDU5WjAcMRowGAYDVQQDDBF0Y3BiaW4uY29tLWNsaWVudDCCASIw
7
+ DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANUBILPvNb3OupTXJwW2AxwQhBWK
8
+ mvQlRHiDEcQM1EHnhH1DjANCSgPoV6ngaGa1Vt+p5Qe1uXc54eJ7y5yJ00i8Lg5c
9
+ L6t9fvhfjXCyWqh78XeL9jVTlwV9Bpz0BsfO91SSC7VEu/QA0syJGXTxdopnH0O0
10
+ /Wy9IX+SeZQzKscwk2G8ceVB2R5bi+48u3BE5UuzLsyZaVfzMS4zwHm2ADY3wj02
11
+ EmmTtQ0OMF8oxH1BP+UXwEX+Exen9i0VakBCgJqYzvy3tHrMZKylrVq5st46keSI
12
+ 7A666NMpEhMLI5kz25EDKlr7KVhim9jlfdUo1ASqN/7Bln158ycN0E6dObUCAwEA
13
+ AaNCMEAwHQYDVR0OBBYEFGrQ9rG00D/5IFX1xGLoKNNXMKn4MB8GA1UdIwQYMBaA
14
+ FOLuMowBSAZfV5v82LmlaIIOvU/DMA0GCSqGSIb3DQEBCwUAA4IBAQACxSfDvc4P
15
+ osb9nP+UZH7B3D895N6OG1f1HQcBHu5V6FxiUSmLFetdUOwV3DeHUHkX2uQ6gWrt
16
+ EuhMI/BZQBI8tZvAe95Clfyxi0qGKoa0deyUdvAqdUoPGZJ9gAoSR/hb5e9zFjPk
17
+ p5hEpwZnbQsCABlSwc3oS74W5iuelOZmA6elR6CDEnvlNlX/0FYXzbyKrNtSfDFG
18
+ MgZv0lLSKkdtm0dgR7wgWmWf52ofG4hzDA9e1GtrAJ4SjVPy9hpOQu7syCNk8uCi
19
+ LKmXoJ1iVPB46QStpwNjLb6Tgre92py1QdKGsiV+26X+KQh1G4bcD6YZQ6EDXwq9
20
+ Hx7OyvVk0y8z
21
21
  -----END CERTIFICATE-----
@@ -1,28 +1,28 @@
1
1
  -----BEGIN PRIVATE KEY-----
2
- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5i5M+eHwqEkjP
3
- 7hveORr4Wr0BfLp7BA3dEbh1mTyAXK5RM4Sy4oonoBE+FBZcsBeMEn24jRoMK91M
4
- PuqYd8S6zbPdQRGKjhnrE/cETzJaJ/PKrGlSBX26n4OHtZtLIvvzcGTJPuWwXmAt
5
- G5V5Ox4k2eSibUDS3nbiGGj0rsqvqsZf8Q6ljon2ynIMnIrYAjyj8aIbcGuYeiI1
6
- N1mG+/sTT7d3MfoaAsH2m9TYFQbHumbFH/NZ5AhFSii2jb5Q5Jl6fSLamWWsvtP8
7
- sywoDcn/IA6a1PLvpwzPqXM/NzrSEFlXjeTLRY5AOJ4pOecqnZapwdTlsxM2jEjI
8
- elbMtd8TAgMBAAECggEAfLWcfSOcSObLO76NyppVT1IlsWc1K9O4wbrUYW5iZOBm
9
- Zbub2GQ9eY6zqCb2NMxCt2oCSFXGiSG+dy3eniX5+5ig6PiAIsGKGB/uKl5UuJYb
10
- 3UBu9astK49lZ4Sf4Sudbq0/gKge16FHQWpF2BrtEtXFP4rxRAo0m5jOio8lOlYG
11
- VSBFrkww7aS0pbymELj+Yg0twrUQjeG/hhoA4juiAq8T6oilrNfxhgc8pi2e23Ix
12
- iokPjVWWAylKr6+oKhgBj/MKoIcEPeyYem1rBCesO2Th5CHy4YwIsZq+456Wo9/z
13
- efxjnr+1vrsH+WK83nff3wUMruchKbdP1p9sMSkwqQKBgQD0y8b7sK4omV/JRdS3
14
- sADYETu2wshP22EuUbkDPICe5/ct1LWlTJAI84MkMcg4rOHuLubD32ttRA3SWnK/
15
- eqXB3gkcstVhipjj5qH5sg3gCIOa5rmePY1Zn5v75BImYzHYBfqQ0Z99czBNQ4un
16
- MPMxe1IRT+b6aU7EOWjSWG2X/wKBgQDCCZGpv+pFQqxb5YjPQqDn/7M9wE89b22T
17
- G2weESzoBrnLCUA4M0QEhhgBkcb9grZQ0aCOzjqKTDF/2AnFOhP6IIzEVxkrsC/0
18
- c150lU8kiIlkymVF7N5vwayPFUeNqaO5NdHOYu0HkEQ8h9FCFurc0OjA/8xRPXkp
19
- AGar8P/Y7QKBgE+5jjSqdg4C5Y9Hjt/EEoJMGoaLKXHYoO3U78x+B+W45memvwH2
20
- zXIc3LkM/Yh3xZ0s6TshqHsNjvLTQkvaReG9znnqRFRgLysKEfagZqRwIWxxeEJx
21
- CXgG42ZGASM/axxP1isUGj1hJnoDZZgt+QZEg5Xfz/n+EgkWKW1YH1lBAoGAMw7L
22
- ioxae+Egc4oBpvAUYRfStXQOJc9VWPlFSOAiHefvKbMEeAVdZ4dVd8xBPWIQ0VFn
23
- 20v+8Xc9KzPQ1loC+bVo9R0qHWneJIfbGfhT+/wFk0UCwxSiL2waGQhzbJ5v24OC
24
- 8rjrQCtBGWBvuuFG6dX6+RYWUGZJpHVbjvD6kb0CgYAnU00V1AZu39ku1iozaHoY
25
- STedxCDtGZotOISDFzaqIxaJ1JUeWZ/90ZMIVHdE+pP8n2P8MN7rppBbTN6Mxcbq
26
- uE9BfZJGN5A8PuugoJWBJwkfnhrEMXXSpz5SPQntCkrET7pkAW2Nx1CWRQKaj1pu
27
- +CKEVUIhv/elJOdMV9mGbA==
2
+ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVASCz7zW9zrqU
3
+ 1ycFtgMcEIQVipr0JUR4gxHEDNRB54R9Q4wDQkoD6Fep4GhmtVbfqeUHtbl3OeHi
4
+ e8ucidNIvC4OXC+rfX74X41wslqoe/F3i/Y1U5cFfQac9AbHzvdUkgu1RLv0ANLM
5
+ iRl08XaKZx9DtP1svSF/knmUMyrHMJNhvHHlQdkeW4vuPLtwROVLsy7MmWlX8zEu
6
+ M8B5tgA2N8I9NhJpk7UNDjBfKMR9QT/lF8BF/hMXp/YtFWpAQoCamM78t7R6zGSs
7
+ pa1aubLeOpHkiOwOuujTKRITCyOZM9uRAypa+ylYYpvY5X3VKNQEqjf+wZZ9efMn
8
+ DdBOnTm1AgMBAAECggEARaCzvv3P9HbSWPsnv18rDw57DsubMXnJMxetRAfpjo2O
9
+ qp/c8efGaBaYKWi41/IpLr3Lp0SJFuct5qoO+eG31kvlRj5uOsGwMqKRiqhSqEaz
10
+ vR9cYTws3tdqxP2kBcaq5NNEzoFkazOltMSQNMEFveJNvwU33kbI33nTElXTgv7O
11
+ EPo/+Xz4Vktk3F5yFHTQi+cIiql6F4QOHmKxMLKRFt85IJ2UCK+aJc1ehzXeYWcZ
12
+ YQbD4Xp0TpWopiv6YMJ+M+ibe+SjO9mA2nfEPn7s6DMEDRLNgxJjAkzuN3TACddY
13
+ 6jyyqVPN14axxWq2VMmmLPpMjBf4aaLDJPu07FlmVQKBgQD3lshvGvcgNBFlVjzB
14
+ DMYpfFyk2QUUrBgj1r0bpf19vvpRr69HufkhaBCOdMILJCYud1CM7nMwM27oOKeO
15
+ SGUGhaGBAAUa0eDgNyqxRX1lnClI7aUiAQlINoIBk6jb5qD62qnhHdoHCR4Ru8LY
16
+ wdRhpf5w+jxLW/+PwK1Pv6tOnwKBgQDcPZJVgnlysfYthwcPFjqkz1riHtx8hh3U
17
+ 8gYqa7yZWCenvSNQZphVCOiY0J7wzcDyDBZF+AAFQjJAlohC/YfnrjA0a6lr2bzq
18
+ tqWmGefoXVmK1JuOztnou4gSj27POY3EJTYhjaQ1hVuihp9Fo33/HmMzPfgQpC6/
19
+ s/f3eEvbKwKBgQCP4CtxhTX+nMNPJCIB/S5ahU5A7WwqaydMDEmwe3EnVYeZWIJ/
20
+ J+9CHnsgjXEFkGgvsF46x4ZgiEL255VW9XLfq2AC2sQcpcIMCYLhqQQJvAmxu4eu
21
+ jvOZ5zL9P5Vs1ETQAamei/5bAE1c+MNtupV0eUW22XOYR0nne4w7P6KI+QKBgH6G
22
+ WNULGd5MYpMRKXr/WD1qalnRRPW8ztKRH/2q2zR1MoLo46rC5eykK4vu/gB1E816
23
+ KCmWKdzbhKcaU7m4kSUGKudSmog9FIz61PsxksspJdHeBmfqacGMSsXu5Mfj1o/C
24
+ mPB5wMaGkHg0QrftDutLd83uYd6dk8XKDEB9OYb1AoGBAOzSZdMyFqhcks4y2UyO
25
+ UN7XXJojGPdYVRlQcVtlsYARqdqzKDzRQ0xmchU6y8JpqkPC700UvWrDHjLYJe3N
26
+ QBLk19dNseNEssiwESerWJVEk4QniGr2BnLboB3oWnUAvR+x5mVZJ1L9lXpFDmG5
27
+ QEuVAN4VEgS37CP5U7+5MzJi
28
28
  -----END PRIVATE KEY-----
@@ -47,7 +47,14 @@ if RMQClient.is_available():
47
47
 
48
48
  def close(self, reply_code=0, reply_text="Normal shutdown"):
49
49
  self.__is_closing = True
50
- super().close(reply_code=reply_code, reply_text=reply_text)
50
+ try:
51
+ super().close(reply_code=reply_code, reply_text=reply_text)
52
+ except ValueError as exc:
53
+ if self.__rapid_close and "file descriptor cannot be a negative integer" in str(exc):
54
+ # This exception can appear during channel close with rapid close option
55
+ pass
56
+ else:
57
+ raise exc
51
58
 
52
59
  def _flush_output(self, *waiters):
53
60
  if self.__rapid_close and self.__is_closing:
@@ -60,11 +67,18 @@ if RMQClient.is_available():
60
67
  """
61
68
  Override start_consuming method to add time_limit parameter.
62
69
  """
70
+ if Tools.do_log(logger, logging.DEBUG):
71
+ logger.debug(f"[{self}] Start consuming...")
63
72
  self.__is_consuming = True
64
73
  try:
65
74
  if not self.__rapid_close and time_limit is None and time_limit_on_stop is None:
66
75
  # Use pika implementation
76
+ if Tools.do_log(logger, logging.DEBUG):
77
+ logger.debug(f"[{self}] Start consuming with Pika implementation")
67
78
  return super().start_consuming()
79
+ else:
80
+ if Tools.do_log(logger, logging.DEBUG):
81
+ logger.debug(f"[{self}] Start consuming with HolAdo implementation")
68
82
 
69
83
  # Check if called from the scope of an event dispatch callback
70
84
  with self.connection._acquire_event_dispatch() as dispatch_allowed:
@@ -81,14 +95,22 @@ if RMQClient.is_available():
81
95
  if self.__is_stopping_consuming:
82
96
  t_limit = time_limit_on_stop
83
97
  if self.__rapid_close and t_limit is None:
84
- # To ensure a rapid close, time_limit shouldn't be None, set it to 0
98
+ # To ensure a rapid close, time_limit shouldn't be None, set it to 0 while stopping consuming
85
99
  t_limit = 0
86
100
  else:
87
101
  t_limit = time_limit
102
+ if t_limit is None and self.__rapid_close:
103
+ # To ensure a rapid close, time_limit shouldn't be None, set it to 0.1s as a compromise between rapidity and resource cost
104
+ t_limit = 0.1
105
+
106
+ if Tools.do_log(logger, logging.TRACE): # @UndefinedVariable
107
+ logger.trace(f"[{self}] Processing data events (time_limit={t_limit})")
88
108
  self._process_data_events(time_limit=t_limit)
89
109
  finally:
90
110
  self.__is_consuming = False
91
111
  self.__is_stopping_consuming = False
112
+ if Tools.do_log(logger, logging.DEBUG):
113
+ logger.debug(f"[{self}] Finished start consuming")
92
114
 
93
115
  def stop_consuming(self, consumer_tag=None):
94
116
  self.__is_stopping_consuming = True
@@ -562,12 +562,12 @@ class RMQConsumer(DeleteableObject):
562
562
  super().__init__(f"[{self.name}] Stop consuming on consumer {self.consumer_tag}")
563
563
 
564
564
  def _process(self2): # @NoSelf
565
- time.sleep(0.1)
566
- GcManager.collect(max_duration_until_next_collect_in_seconds=0)
567
565
  return self.is_consuming
568
566
  redo = StopRedo()
569
567
  redo.redo_while(True)
570
568
  redo.with_timeout(30*60) # Timeout of 30 min
569
+ redo.polling_every(0.01) # Wait 0.1 s beetween each try
570
+ redo.with_process_in_thread(False) # Run process in thread is not needed
571
571
  redo.execute()
572
572
 
573
573
  if Tools.do_log(logger, logging.DEBUG):
@@ -0,0 +1,129 @@
1
+
2
+ #################################################
3
+ # HolAdo (Holistic Automation do)
4
+ #
5
+ # (C) Copyright 2021-2025 by Eric Klumpp
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
+ #
9
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+
11
+ # The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
12
+ #################################################
13
+
14
+ import logging
15
+ from holado_report.report.builders.report_builder import ReportBuilder
16
+ from holado_core.common.tools.tools import Tools
17
+ from holado_core.common.exceptions.technical_exception import TechnicalException
18
+ import json
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+
24
+ class FailureReportBuilder(ReportBuilder):
25
+ """Failure report builder
26
+ Supported formats: 'txt', 'json', 'xml'
27
+ """
28
+ def __init__(self, filepath, file_format='xml'):
29
+ if file_format not in ['txt', 'json', 'xml']:
30
+ raise TechnicalException(f"Unmanaged format '{file_format}' (possible formats: 'txt', 'json')")
31
+
32
+ self.__filepath = filepath
33
+ self.__file_format = file_format
34
+ self.__failures = {}
35
+
36
+ def build(self):
37
+ '''
38
+ The file is built after each scenario
39
+ '''
40
+ pass
41
+
42
+ def after_scenario(self, scenario, scenario_report=None):
43
+ from holado_report.report.report_manager import ReportManager
44
+ status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
45
+
46
+ if status_validation != "Passed":
47
+ step_error_message = ReportManager.get_step_error_message(step_failed).strip()
48
+ if step_error_message not in self.__failures:
49
+ self.__failures[step_error_message] = []
50
+
51
+ if self.__file_format == 'txt':
52
+ msg_list = []
53
+ msg_list.append(f"scenario in {scenario.filename} at l.{scenario.line} - step {step_number} (l.{step_failed.line}) - {status_validation}")
54
+ msg_list.append(f" Feature/Scenario: {scenario.feature.name} => {scenario.name}")
55
+ msg_list.append(f" Report: {scenario_report.report_path}")
56
+ msg_list.append(f" Tags: -t " + " -t ".join(scenario.feature.tags + scenario.tags))
57
+ msg_scenario = "\n".join(msg_list)
58
+
59
+ self.__failures[step_error_message].append(msg_scenario)
60
+ elif self.__file_format in ['json', 'xml']:
61
+ scenario_info = {
62
+ 'title': f"{scenario.filename} - l.{scenario.line} - step {step_number} (l.{step_failed.line}) - {status_validation}",
63
+ 'scenario': f"{scenario.feature.name} => {scenario.name}",
64
+ 'report': scenario_report.report_path,
65
+ 'tags': "-t " + " -t ".join(scenario.feature.tags + scenario.tags),
66
+ }
67
+ self.__failures[step_error_message].append(scenario_info)
68
+ else:
69
+ raise TechnicalException(f"Unmanaged format '{self.__file_format}' (possible formats: 'txt', 'json')")
70
+
71
+ self.__update_file()
72
+
73
+ def __update_file(self):
74
+ with open(self.__filepath, "wt") as fout:
75
+ if self.__file_format == 'txt':
76
+ for failure, scenarios_messages in self.__failures.items():
77
+ fout.write(failure + "\n")
78
+ fout.write("\n")
79
+ for msg in scenarios_messages:
80
+ fout.write(Tools.indent_string(4, msg) + "\n")
81
+ fout.write("\n")
82
+ elif self.__file_format == 'json':
83
+ json_str = json.dumps(self.__failures, ensure_ascii=False, indent=4)
84
+ fout.write(json_str)
85
+ elif self.__file_format == 'xml':
86
+ self.__file_write_failures(fout, self.__failures, sort_by_nb_scenario=True)
87
+ else:
88
+ raise TechnicalException(f"Unmanaged format '{self.__file_format}' (possible formats: 'txt', 'json')")
89
+
90
+ def __file_write_failures(self, fout, __failures, sort_by_nb_scenario=True):
91
+ fout.write("<failures>\n")
92
+
93
+ if sort_by_nb_scenario:
94
+ for error_message, scenarios in sorted(self.__failures.items(), key=lambda item:-len(item[1])):
95
+ self.__file_write_failure(fout, error_message, scenarios)
96
+ else:
97
+ for error_message, scenarios in self.__failures.items():
98
+ self.__file_write_failure(fout, error_message, scenarios)
99
+
100
+ fout.write("</failures>\n")
101
+
102
+ def __file_write_failure(self, fout, error_message, scenarios):
103
+ msg_list = []
104
+
105
+ msg_list.append(" <failure>")
106
+
107
+ if "\n" in error_message:
108
+ msg_list.append(f" <error_message>")
109
+ msg_list.append(Tools.indent_string(12, error_message))
110
+ msg_list.append(f" </error_message>")
111
+ else:
112
+ msg_list.append(f" <error_message>{error_message}</error_message>")
113
+
114
+ msg_list.append(f" <scenarios>")
115
+ for scenario_info in scenarios:
116
+ msg_list.append(f" <scenario>")
117
+ for key, value in scenario_info.items():
118
+ msg_list.append(f" <{key}>{value}</{key}>")
119
+ msg_list.append(f" </scenario>")
120
+ msg_list.append(f" </scenarios>")
121
+
122
+ msg_list.append(" </failure>")
123
+
124
+ msg = "\n".join(msg_list) + "\n"
125
+ fout.write(msg)
126
+
127
+
128
+
129
+
@@ -23,6 +23,7 @@ from holado_report.report.builders.short_scenario_failed_report_builder import S
23
23
  from holado_report.report.reports.base_report import BaseReport
24
24
  from holado_scripting.common.tools.evaluate_parameters import EvaluateParameters
25
25
  from holado_report.report.builders.summary_scenario_report_builder import SummaryScenarioReportBuilder
26
+ from holado_report.report.builders.failure_report_builder import FailureReportBuilder
26
27
  # from holado_core.scenario.scenario_duration_manager import ScenarioDurationManager
27
28
 
28
29
  logger = logging.getLogger(__name__)
@@ -65,6 +66,9 @@ class ReportManager(BaseReport):
65
66
  fn = self.get_path("report_summary_scenario_all.txt")
66
67
  self.add_report_builder(SummaryScenarioReportBuilder(fn))
67
68
 
69
+ fn = self.get_path("report_failures.xml")
70
+ self.add_report_builder(FailureReportBuilder(fn))
71
+
68
72
  fn = self.get_path("report_short_scenario_failed.txt")
69
73
  self.add_report_builder(ShortScenarioFailedReportBuilder(fn))
70
74
 
@@ -16,6 +16,8 @@ from holado_core.common.exceptions.technical_exception import TechnicalException
16
16
  import logging
17
17
  from holado_core.common.tools.tools import Tools
18
18
  import json
19
+ from holado_core.common.tools.converters.converter import Converter
20
+ from holado_json.ipc.json_converter import JsonConverter
19
21
 
20
22
  logger = logging.getLogger(__name__)
21
23
 
@@ -116,17 +118,27 @@ class RestClient(object):
116
118
  if self.__headers:
117
119
  res['headers'] = self.__headers
118
120
  if request_kwargs:
119
- # Ensure data is in json string format
120
- if 'data' in request_kwargs:
121
- data = request_kwargs.pop('data')
122
- if data is not None and not isinstance(data, str):
123
- data = json.dumps(data)
124
- res['data'] = data
125
-
121
+ # Add all kwargs except data
122
+ data = request_kwargs.pop('data', None)
126
123
  for key in list(request_kwargs.keys()):
127
124
  if key in res and isinstance(res[key], dict):
128
125
  res[key].update(request_kwargs.pop(key))
129
126
  res.update(request_kwargs)
127
+
128
+ # Ensure data is in right format
129
+ if data is not None:
130
+ # Convert data to json object for types not supported by requests.request
131
+ if not (isinstance(data, str) or Converter.is_dict(data) or Converter.is_list(data) or isinstance(data, bytes) or Converter.is_file_like(data)):
132
+ converter = JsonConverter()
133
+ data = converter.to_json(data)
134
+
135
+ # Convert data to string for some content types
136
+ if not isinstance(data, str):
137
+ content_type = res['headers']['Content-Type'] if 'headers' in res and 'Content-Type' in res['headers'] else None
138
+ if content_type is not None and 'application/json' in content_type:
139
+ data = json.dumps(data)
140
+ res['data'] = data
141
+
130
142
  return res
131
143
 
132
144
 
@@ -112,8 +112,7 @@ if RestClient.is_available():
112
112
  # Manage request arguments as step table
113
113
  if hasattr(context, "table") and context.table is not None:
114
114
  table = BehaveStepTools.convert_step_table_2_value_table_with_header(context.table)
115
- json_obj = ValueTableConverter.convert_name_value_table_2_json_object(table)
116
- data = json.dumps(json_obj)
115
+ data = ValueTableConverter.convert_name_value_table_2_json_object(table)
117
116
 
118
117
  # Manage request arguments as json text
119
118
  # Manage data as json text
@@ -0,0 +1,38 @@
1
+ @ais
2
+ @type_10
3
+ Feature: Message type 10 : Coordinated UTC Inquiry
4
+
5
+ Scenario: Create, convert, encode and decode message
6
+ # Create message
7
+ When MESSAGE = new AIS message of type 'T10_COORDINATED_UTC_INQUIRY'
8
+ | repeat | mmsi | dest_mmsi |
9
+ | 1 | 235006280 | 23200002 |
10
+
11
+ # Verify bytes conversion
12
+ When MESSAGE_TO_BYTES = convert AIS message MESSAGE to bytes
13
+ When MESSAGE_BYTES = new AIS message of type 'T10_COORDINATED_UTC_INQUIRY' as bitarray bytes
14
+ | repeat | mmsi | dest_mmsi |
15
+ | 1 | 235006280 | 23200002 |
16
+ Then MESSAGE_TO_BYTES == MESSAGE_BYTES
17
+
18
+ # Encode in NMEA
19
+ When NMEA_SENTENCES = encode AIS raw payload MESSAGE_BYTES to NMEA
20
+ | talker_id | radio_channel | seq_id | group_id |
21
+ | 'AIVDM' | 'A' | 0 | 0 |
22
+ Then ${len(NMEA_SENTENCES)} == 1
23
+
24
+ # Verify some NMEA fields
25
+ Given STRING_NMEA_MSG = split NMEA AIS message NMEA_SENTENCES[0] to fields
26
+ Then STRING_NMEA_MSG[-3] == 'A'
27
+
28
+ # Decode NMEA message
29
+ Given DECODED_NMEA_MESSAGE = decode NMEA AIS message NMEA_SENTENCES as dictionnary
30
+ When DECODED_NMEA_MESSAGE_TABLE = convert dictionary DECODED_NMEA_MESSAGE to table with keys as columns
31
+ Then table DECODED_NMEA_MESSAGE_TABLE is
32
+ | dest_mmsi | mmsi | msg_type | repeat |
33
+ | 23200002 | 235006280 | 10 | 1 |
34
+
35
+
36
+
37
+
38
+
@@ -0,0 +1,37 @@
1
+ @ais
2
+ @type_12
3
+ Feature: Message type 12 : Addressed Safety Related Message
4
+
5
+ Scenario: Create, convert, encode and decode message
6
+ # Create message
7
+ When MESSAGE = new AIS message of type 'T12_ADDRESSED_SAFETY_RELATED_MESSAGE'
8
+ | repeat | mmsi | seqno | dest_mmsi | retransmit | text |
9
+ | 1 | 235006280 | 3 | 23200002 | True | 'TYPE 12' |
10
+
11
+ # Verify bytes conversion
12
+ When MESSAGE_TO_BYTES = convert AIS message MESSAGE to bytes
13
+ When MESSAGE_BYTES = new AIS message of type 'T12_ADDRESSED_SAFETY_RELATED_MESSAGE' as bitarray bytes
14
+ | repeat | mmsi | seqno | dest_mmsi | retransmit | text |
15
+ | 1 | 235006280 | 3 | 23200002 | True | 'TYPE 12' |
16
+ Then MESSAGE_TO_BYTES == MESSAGE_BYTES
17
+
18
+ # Encode in NMEA
19
+ When NMEA_SENTENCES = encode AIS raw payload MESSAGE_BYTES to NMEA
20
+ | talker_id | radio_channel | seq_id | group_id |
21
+ | 'AIVDM' | 'A' | 0 | 0 |
22
+ Then ${len(NMEA_SENTENCES)} == 1
23
+
24
+ # Verify some NMEA fields
25
+ Given STRING_NMEA_MSG = split NMEA AIS message NMEA_SENTENCES[0] to fields
26
+ Then STRING_NMEA_MSG[-3] == 'A'
27
+
28
+ # Decode NMEA message
29
+ Given DECODED_NMEA_MESSAGE = decode NMEA AIS message NMEA_SENTENCES as dictionnary
30
+ When DECODED_NMEA_MESSAGE_TABLE = convert dictionary DECODED_NMEA_MESSAGE to table with keys as columns
31
+ Then table DECODED_NMEA_MESSAGE_TABLE is
32
+ | dest_mmsi | mmsi | msg_type | repeat | retransmit | seqno | text |
33
+ | 23200002 | 235006280 | 12 | 1 | True | 3 | 'TYPE 12' |
34
+
35
+
36
+
37
+
@@ -0,0 +1,36 @@
1
+ @ais
2
+ @type_14
3
+ Feature: Message type 14 : Safety Related Broadcast Message
4
+
5
+ Scenario: Create, convert, encode and decode message
6
+ # Create message
7
+ When MESSAGE = new AIS message of type 'T14_SAFETY_RELATED_BROADCAST_MESSAGE'
8
+ | repeat | mmsi | text |
9
+ | 1 | 235006280 | 'Test for type 14' |
10
+
11
+ # Verify bytes conversion
12
+ When MESSAGE_TO_BYTES = convert AIS message MESSAGE to bytes
13
+ When MESSAGE_BYTES = new AIS message of type 'T14_SAFETY_RELATED_BROADCAST_MESSAGE' as bitarray bytes
14
+ | repeat | mmsi | text |
15
+ | 1 | 235006280 | 'Test for type 14' |
16
+ Then MESSAGE_TO_BYTES == MESSAGE_BYTES
17
+
18
+ # Encode in NMEA
19
+ When NMEA_SENTENCES = encode AIS raw payload MESSAGE_BYTES to NMEA
20
+ | talker_id | radio_channel | seq_id | group_id |
21
+ | 'AIVDM' | 'A' | 0 | 0 |
22
+ Then ${len(NMEA_SENTENCES)} == 1
23
+
24
+ # Verify some NMEA fields
25
+ Given STRING_NMEA_MSG = split NMEA AIS message NMEA_SENTENCES[0] to fields
26
+ Then STRING_NMEA_MSG[-3] == 'A'
27
+
28
+ # Decode NMEA message
29
+ Given DECODED_NMEA_MESSAGE = decode NMEA AIS message NMEA_SENTENCES as dictionnary
30
+ When DECODED_NMEA_MESSAGE_TABLE = convert dictionary DECODED_NMEA_MESSAGE to table with keys as columns
31
+ Then table DECODED_NMEA_MESSAGE_TABLE is
32
+ | mmsi | msg_type | repeat | text |
33
+ | 235006280 | 14 | 1 | 'TEST FOR TYPE 14' |
34
+
35
+
36
+
@@ -0,0 +1,36 @@
1
+ @ais
2
+ @type_15
3
+ Feature: Message type 15 : Interrogation
4
+
5
+ Scenario: Create, convert, encode and decode message
6
+ # Create message
7
+ When MESSAGE = new AIS message of type 'T15_INTERROGATION'
8
+ | repeat | mmsi | mmsi1 | type1_1 | offset1_1 | type1_2 | offset1_2 | mmsi2 | type2_1 | offset2_1 |
9
+ | 1 | 235006280 | 23200001 | 1 | 1000 | 27 | 2000 | 23200002 | 30 | 3000 |
10
+
11
+ # Verify bytes conversion
12
+ When MESSAGE_TO_BYTES = convert AIS message MESSAGE to bytes
13
+ When MESSAGE_BYTES = new AIS message of type 'T15_INTERROGATION' as bitarray bytes
14
+ | repeat | mmsi | mmsi1 | type1_1 | offset1_1 | type1_2 | offset1_2 | mmsi2 | type2_1 | offset2_1 |
15
+ | 1 | 235006280 | 23200001 | 1 | 1000 | 27 | 2000 | 23200002 | 30 | 3000 |
16
+ Then MESSAGE_TO_BYTES == MESSAGE_BYTES
17
+
18
+ # Encode in NMEA
19
+ When NMEA_SENTENCES = encode AIS raw payload MESSAGE_BYTES to NMEA
20
+ | talker_id | radio_channel | seq_id | group_id |
21
+ | 'AIVDM' | 'A' | 0 | 0 |
22
+ Then ${len(NMEA_SENTENCES)} == 1
23
+
24
+ # Verify some NMEA fields
25
+ Given STRING_NMEA_MSG = split NMEA AIS message NMEA_SENTENCES[0] to fields
26
+ Then STRING_NMEA_MSG[-3] == 'A'
27
+
28
+ # Decode NMEA message
29
+ Given DECODED_NMEA_MESSAGE = decode NMEA AIS message NMEA_SENTENCES as dictionnary
30
+ When DECODED_NMEA_MESSAGE_TABLE = convert dictionary DECODED_NMEA_MESSAGE to table with keys as columns
31
+ Then table DECODED_NMEA_MESSAGE_TABLE is
32
+ | mmsi | mmsi1 | mmsi2 | msg_type | offset1_1 | offset1_2 | offset2_1 | repeat | type1_1 | type1_2 | type2_1 |
33
+ | 235006280 | 23200001 | 23200002 | 15 | 1000 | 2000 | 3000 | 1 | 1 | 27 | 30 |
34
+
35
+
36
+
@@ -0,0 +1,38 @@
1
+ @ais
2
+ @type_16
3
+ Feature: Message type 16 : Assigned Mode Command
4
+
5
+ Scenario: Create, convert, encode and decode message
6
+ # Create message
7
+ When MESSAGE = new AIS message of type 'T16_ASSIGNED_MODE_COMMAND'
8
+ | repeat | mmsi | mmsi1 | offset1 | increment1 | mmsi2 | offset2 | increment2 |
9
+ | 1 | 235006280 | 232 | 501 | 1001 | 23200002 | 102 | 1002 |
10
+
11
+ # Verify bytes conversion
12
+ When MESSAGE_TO_BYTES = convert AIS message MESSAGE to bytes
13
+ When MESSAGE_BYTES = new AIS message of type 'T16_ASSIGNED_MODE_COMMAND' as bitarray bytes
14
+ | repeat | mmsi | mmsi1 | offset1 | increment1 | mmsi2 | offset2 | increment2 |
15
+ | 1 | 235006280 | 232 | 501 | 1001 | 23200002 | 102 | 1002 |
16
+ Then MESSAGE_TO_BYTES == MESSAGE_BYTES
17
+
18
+ # Encode in NMEA
19
+ When NMEA_SENTENCES = encode AIS raw payload MESSAGE_BYTES to NMEA
20
+ | talker_id | radio_channel | seq_id | group_id |
21
+ | 'AIVDM' | 'A' | 0 | 0 |
22
+ Then ${len(NMEA_SENTENCES)} == 1
23
+
24
+ # Verify some NMEA fields
25
+ Given STRING_NMEA_MSG = split NMEA AIS message NMEA_SENTENCES[0] to fields
26
+ Then STRING_NMEA_MSG[-3] == 'A'
27
+
28
+ # Decode NMEA message
29
+ Given DECODED_NMEA_MESSAGE = decode NMEA AIS message NMEA_SENTENCES as dictionnary
30
+ When DECODED_NMEA_MESSAGE_TABLE = convert dictionary DECODED_NMEA_MESSAGE to table with keys as columns
31
+ Then table DECODED_NMEA_MESSAGE_TABLE is
32
+ | increment1 | increment2 | mmsi | mmsi1 | mmsi2 | msg_type | offset1 | offset2 | repeat |
33
+ | 1001 | 1002 | 235006280 | 232 | 23200002 | 16 | 501 | 102 | 1 |
34
+
35
+
36
+
37
+
38
+
@@ -0,0 +1,46 @@
1
+ @ais
2
+ @type_17
3
+ Feature: Message type 17 : GNSS Broadcast Binary Message
4
+
5
+ Scenario: Create, convert, encode and decode message
6
+ ### PRECONDITIONS - BEGIN
7
+ Given begin preconditions
8
+
9
+ Given LON = 90.5
10
+ Given LAT = 44.5
11
+
12
+ Given end preconditions
13
+ ### PRECONDITIONS - END
14
+
15
+ # Create message
16
+ When MESSAGE = new AIS message of type 'T17_GNSS_BROADCAST_BINARY_MESSAGE'
17
+ | repeat | mmsi | lon | lat | data |
18
+ | 1 | 235006280 | LON | LAT | b'1' |
19
+
20
+ # Verify bytes conversion
21
+ When MESSAGE_TO_BYTES = convert AIS message MESSAGE to bytes
22
+ When MESSAGE_BYTES = new AIS message of type 'T17_GNSS_BROADCAST_BINARY_MESSAGE' as bitarray bytes
23
+ | repeat | mmsi | lon | lat | data |
24
+ | 1 | 235006280 | LON | LAT | b'1' |
25
+ Then MESSAGE_TO_BYTES == MESSAGE_BYTES
26
+
27
+ # Encode in NMEA
28
+ When NMEA_SENTENCES = encode AIS raw payload MESSAGE_BYTES to NMEA
29
+ | talker_id | radio_channel | seq_id | group_id |
30
+ | 'AIVDM' | 'A' | 0 | 0 |
31
+ Then ${len(NMEA_SENTENCES)} == 1
32
+
33
+ # Verify some NMEA fields
34
+ Given STRING_NMEA_MSG = split NMEA AIS message NMEA_SENTENCES[0] to fields
35
+ Then STRING_NMEA_MSG[-3] == 'A'
36
+
37
+ # Decode NMEA message
38
+ Given DECODED_NMEA_MESSAGE = decode NMEA AIS message NMEA_SENTENCES as dictionnary
39
+ When DECODED_NMEA_MESSAGE_TABLE = convert dictionary DECODED_NMEA_MESSAGE to table with keys as columns
40
+ Then table DECODED_NMEA_MESSAGE_TABLE is
41
+ | data | lat | lon | mmsi | msg_type | repeat |
42
+ | b'1' | LAT | LON | 235006280 | 17 | 1 |
43
+
44
+
45
+
46
+
@@ -0,0 +1,37 @@
1
+ @ais
2
+ @type_18
3
+ Feature: Message type 18 : Standard Class B Position Report
4
+
5
+ Scenario: Create, convert, encode and decode message
6
+ # Create message
7
+ When MESSAGE = new AIS message of type 'T18_STANDARD_CLASS_B_POSITION_REPORT'
8
+ | repeat | mmsi | reserved_1 | speed | accuracy | lon | lat | course | heading | second | reserved_2 | cs | display | dsc | band | msg22 | raim | radio | assigned |
9
+ | 1 | 235006280 | 1 | 20.2 | True | 44.3 | 22.7 | 67 | 1 | 2 | 1 | True | True | True | True | True | True | 11 | True |
10
+
11
+ # Verify bytes conversion
12
+ When MESSAGE_TO_BYTES = convert AIS message MESSAGE to bytes
13
+ When MESSAGE_BYTES = new AIS message of type 'T18_STANDARD_CLASS_B_POSITION_REPORT' as bitarray bytes
14
+ | repeat | mmsi | reserved_1 | speed | accuracy | lon | lat | course | heading | second | reserved_2 | cs | display | dsc | band | msg22 | raim | radio | assigned |
15
+ | 1 | 235006280 | 1 | 20.2 | True | 44.3 | 22.7 | 67 | 1 | 2 | 1 | True | True | True | True | True | True | 11 | True |
16
+ Then MESSAGE_TO_BYTES == MESSAGE_BYTES
17
+
18
+ # Encode in NMEA
19
+ When NMEA_SENTENCES = encode AIS raw payload MESSAGE_BYTES to NMEA
20
+ | talker_id | radio_channel | seq_id | group_id |
21
+ | 'AIVDM' | 'A' | 0 | 0 |
22
+ Then ${len(NMEA_SENTENCES)} == 1
23
+
24
+ # Verify some NMEA fields
25
+ Given STRING_NMEA_MSG = split NMEA AIS message NMEA_SENTENCES[0] to fields
26
+ Then STRING_NMEA_MSG[-3] == 'A'
27
+
28
+ # Decode NMEA message
29
+ Given DECODED_NMEA_MESSAGE = decode NMEA AIS message NMEA_SENTENCES as dictionnary
30
+ When DECODED_NMEA_MESSAGE_TABLE = convert dictionary DECODED_NMEA_MESSAGE to table with keys as columns
31
+ Then table DECODED_NMEA_MESSAGE_TABLE is
32
+ | accuracy | assigned | band | course | cs | display | dsc | heading | lat | lon | mmsi | msg22 | msg_type | radio | raim | repeat | reserved_1 | reserved_2 | second | speed |
33
+ | True | True | True | 67.0 | True | True | True | 1 | 22.7 | 44.3 | 235006280 | True | 18 | 11 | True | 1 | 1 | 1 | 2 | 20.2 |
34
+
35
+
36
+
37
+