datadog-ci 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -2
- data/lib/datadog/ci/configuration/components.rb +30 -3
- data/lib/datadog/ci/contrib/cucumber/formatter.rb +13 -9
- data/lib/datadog/ci/contrib/minitest/runnable.rb +5 -1
- data/lib/datadog/ci/contrib/minitest/runner.rb +6 -2
- data/lib/datadog/ci/contrib/minitest/test.rb +7 -3
- data/lib/datadog/ci/contrib/rspec/example.rb +6 -2
- data/lib/datadog/ci/contrib/rspec/example_group.rb +5 -1
- data/lib/datadog/ci/contrib/rspec/knapsack_pro/runner.rb +6 -2
- data/lib/datadog/ci/contrib/rspec/runner.rb +6 -2
- data/lib/datadog/ci/ext/environment/providers/appveyor.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/aws_code_pipeline.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/azure.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/bitbucket.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/bitrise.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/buddy.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/buildkite.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/circleci.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/codefresh.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/github_actions.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/gitlab.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/jenkins.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/teamcity.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/travis.rb +1 -1
- data/lib/datadog/ci/ext/environment.rb +17 -0
- data/lib/datadog/ci/ext/telemetry.rb +120 -0
- data/lib/datadog/ci/git/local_repository.rb +116 -25
- data/lib/datadog/ci/git/search_commits.rb +20 -1
- data/lib/datadog/ci/git/telemetry.rb +37 -0
- data/lib/datadog/ci/git/tree_uploader.rb +7 -0
- data/lib/datadog/ci/git/upload_packfile.rb +22 -1
- data/lib/datadog/ci/test.rb +5 -0
- data/lib/datadog/ci/test_optimisation/component.rb +16 -2
- data/lib/datadog/ci/test_optimisation/coverage/transport.rb +10 -1
- data/lib/datadog/ci/test_optimisation/skippable.rb +24 -0
- data/lib/datadog/ci/test_optimisation/telemetry.rb +56 -0
- data/lib/datadog/ci/test_visibility/component.rb +95 -235
- data/lib/datadog/ci/test_visibility/context.rb +274 -0
- data/lib/datadog/ci/test_visibility/{context → store}/global.rb +1 -1
- data/lib/datadog/ci/test_visibility/{context → store}/local.rb +1 -1
- data/lib/datadog/ci/test_visibility/telemetry.rb +69 -0
- data/lib/datadog/ci/test_visibility/transport.rb +8 -9
- data/lib/datadog/ci/transport/adapters/net.rb +42 -11
- data/lib/datadog/ci/transport/adapters/net_http_client.rb +17 -0
- data/lib/datadog/ci/transport/adapters/telemetry_webmock_safe_adapter.rb +28 -0
- data/lib/datadog/ci/transport/event_platform_transport.rb +42 -5
- data/lib/datadog/ci/transport/http.rb +49 -21
- data/lib/datadog/ci/transport/remote_settings_api.rb +39 -1
- data/lib/datadog/ci/transport/telemetry.rb +93 -0
- data/lib/datadog/ci/utils/identity.rb +20 -0
- data/lib/datadog/ci/utils/parsing.rb +2 -1
- data/lib/datadog/ci/utils/telemetry.rb +23 -0
- data/lib/datadog/ci/version.rb +1 -1
- data/lib/datadog/ci.rb +30 -0
- metadata +16 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57da167788887f67dc501a3dc2869c425754fec3cdefa45770af22364003fb20
|
4
|
+
data.tar.gz: ed6645a822db10fbae793c5b226b9d41ca4dbea67449f545135f2571be3894c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4586bbf8a10b5bf2b262bafefd222ef981624ced261755bd9d43652506400f6b939e5cd3595b7af0c66a193581328937aabe5b9246efd4cb3a0fa088b9e0ccf
|
7
|
+
data.tar.gz: 4169cf67306d9d080e264897ed2cffb5103a9b98ca176fea2784ff3293d4b9dd3decd62df3848b2a7ade783f5dfe6925b86fa7d4727b45de51764af5bc6e1b4a
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.3.0] - 2024-07-30
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
* Add test_session metric ([#207][])
|
8
|
+
* API metrics ([#206][])
|
9
|
+
* git commands telemetry ([#205][])
|
10
|
+
* implement ITR metrics for internal telemetry ([#204][])
|
11
|
+
* Implement code coverage metrics for internal telemetry ([#203][])
|
12
|
+
* Implement manual_api_events metric ([#202][])
|
13
|
+
* HTTP transport metrics and minor telemetry tweaks ([#201][])
|
14
|
+
* Send event_created and event_finished metrics for internal telemetry ([#200][])
|
15
|
+
|
3
16
|
## [1.2.0] - 2024-07-16
|
4
17
|
|
5
18
|
### Changed
|
@@ -277,7 +290,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
277
290
|
|
278
291
|
- Ruby versions < 2.7 no longer supported ([#8][])
|
279
292
|
|
280
|
-
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.
|
293
|
+
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.3.0...main
|
294
|
+
[1.3.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.2.0...v1.3.0
|
281
295
|
[1.2.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.1.0...v1.2.0
|
282
296
|
[1.1.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.1...v1.1.0
|
283
297
|
[1.0.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0...v1.0.1
|
@@ -391,4 +405,12 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
391
405
|
[#189]: https://github.com/DataDog/datadog-ci-rb/issues/189
|
392
406
|
[#190]: https://github.com/DataDog/datadog-ci-rb/issues/190
|
393
407
|
[#193]: https://github.com/DataDog/datadog-ci-rb/issues/193
|
394
|
-
[#197]: https://github.com/DataDog/datadog-ci-rb/issues/197
|
408
|
+
[#197]: https://github.com/DataDog/datadog-ci-rb/issues/197
|
409
|
+
[#200]: https://github.com/DataDog/datadog-ci-rb/issues/200
|
410
|
+
[#201]: https://github.com/DataDog/datadog-ci-rb/issues/201
|
411
|
+
[#202]: https://github.com/DataDog/datadog-ci-rb/issues/202
|
412
|
+
[#203]: https://github.com/DataDog/datadog-ci-rb/issues/203
|
413
|
+
[#204]: https://github.com/DataDog/datadog-ci-rb/issues/204
|
414
|
+
[#205]: https://github.com/DataDog/datadog-ci-rb/issues/205
|
415
|
+
[#206]: https://github.com/DataDog/datadog-ci-rb/issues/206
|
416
|
+
[#207]: https://github.com/DataDog/datadog-ci-rb/issues/207
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "datadog/core/telemetry/ext"
|
4
|
+
|
3
5
|
require_relative "../ext/settings"
|
4
6
|
require_relative "../git/tree_uploader"
|
5
7
|
require_relative "../test_optimisation/component"
|
@@ -11,8 +13,11 @@ require_relative "../test_visibility/null_component"
|
|
11
13
|
require_relative "../test_visibility/serializers/factories/test_level"
|
12
14
|
require_relative "../test_visibility/serializers/factories/test_suite_level"
|
13
15
|
require_relative "../test_visibility/transport"
|
16
|
+
require_relative "../transport/adapters/telemetry_webmock_safe_adapter"
|
14
17
|
require_relative "../transport/api/builder"
|
15
18
|
require_relative "../transport/remote_settings_api"
|
19
|
+
require_relative "../utils/identity"
|
20
|
+
require_relative "../utils/parsing"
|
16
21
|
require_relative "../utils/test_run"
|
17
22
|
require_relative "../worker"
|
18
23
|
|
@@ -59,9 +64,7 @@ module Datadog
|
|
59
64
|
return unless settings.ci.enabled
|
60
65
|
|
61
66
|
# Configure datadog gem for test visibility mode
|
62
|
-
|
63
|
-
# Deactivate telemetry
|
64
|
-
settings.telemetry.enabled = false
|
67
|
+
configure_telemetry(settings)
|
65
68
|
|
66
69
|
# Test visibility uses its own remote settings
|
67
70
|
settings.remote.enabled = false
|
@@ -246,6 +249,30 @@ module Datadog
|
|
246
249
|
end
|
247
250
|
end
|
248
251
|
|
252
|
+
def configure_telemetry(settings)
|
253
|
+
# in development environment Datadog's telemetry is disabled by default
|
254
|
+
# for test visibility we want to enable it by default unless explicitly disabled
|
255
|
+
# NOTE: before agentless mode is released, we only enable telemetry when running with Datadog Agent
|
256
|
+
env_telemetry_enabled = ENV[Core::Telemetry::Ext::ENV_ENABLED]
|
257
|
+
settings.telemetry.enabled = !settings.ci.agentless_mode_enabled &&
|
258
|
+
(env_telemetry_enabled.nil? || Utils::Parsing.convert_to_bool(env_telemetry_enabled))
|
259
|
+
|
260
|
+
return unless settings.telemetry.enabled
|
261
|
+
|
262
|
+
begin
|
263
|
+
require "datadog/core/environment/identity"
|
264
|
+
require "datadog/core/telemetry/http/adapters/net"
|
265
|
+
|
266
|
+
# patch gem's identity to report datadog-ci library version instead of datadog gem version
|
267
|
+
Core::Environment::Identity.include(CI::Utils::Identity)
|
268
|
+
|
269
|
+
# patch gem's telemetry transport layer to use Net::HTTP instead of WebMock's Net::HTTP
|
270
|
+
Core::Telemetry::Http::Adapters::Net.include(CI::Transport::Adapters::TelemetryWebmockSafeAdapter)
|
271
|
+
rescue => e
|
272
|
+
Datadog.logger.warn("Failed to patch Datadog gem's telemetry layer: #{e}")
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
249
276
|
def timecop?
|
250
277
|
Gem.loaded_specs.key?("timecop") || !!defined?(Timecop)
|
251
278
|
end
|
@@ -35,14 +35,14 @@ module Datadog
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def on_test_run_started(event)
|
38
|
-
|
38
|
+
test_visibility_component.start_test_session(
|
39
39
|
tags: {
|
40
40
|
CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
|
41
41
|
CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Cucumber::Integration.version.to_s
|
42
42
|
},
|
43
43
|
service: configuration[:service_name]
|
44
44
|
)
|
45
|
-
|
45
|
+
test_visibility_component.start_test_module(Ext::FRAMEWORK)
|
46
46
|
end
|
47
47
|
|
48
48
|
def on_test_run_finished(event)
|
@@ -70,7 +70,7 @@ module Datadog
|
|
70
70
|
|
71
71
|
start_test_suite(test_suite_name) unless same_test_suite_as_current?(test_suite_name)
|
72
72
|
|
73
|
-
test_span =
|
73
|
+
test_span = test_visibility_component.trace_test(
|
74
74
|
event.test_case.name,
|
75
75
|
test_suite_name,
|
76
76
|
tags: tags,
|
@@ -82,7 +82,7 @@ module Datadog
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def on_test_case_finished(event)
|
85
|
-
test_span =
|
85
|
+
test_span = test_visibility_component.active_test
|
86
86
|
return if test_span.nil?
|
87
87
|
|
88
88
|
finish_span(test_span, event.result)
|
@@ -90,11 +90,11 @@ module Datadog
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def on_test_step_started(event)
|
93
|
-
|
93
|
+
test_visibility_component.trace(event.test_step.to_s, type: Ext::STEP_SPAN_TYPE)
|
94
94
|
end
|
95
95
|
|
96
96
|
def on_test_step_finished(event)
|
97
|
-
current_step_span =
|
97
|
+
current_step_span = test_visibility_component.active_span
|
98
98
|
return if current_step_span.nil?
|
99
99
|
|
100
100
|
finish_span(current_step_span, event.result)
|
@@ -130,8 +130,8 @@ module Datadog
|
|
130
130
|
def finish_session(result)
|
131
131
|
finish_current_test_suite
|
132
132
|
|
133
|
-
test_session =
|
134
|
-
test_module =
|
133
|
+
test_session = test_visibility_component.active_test_session
|
134
|
+
test_module = test_visibility_component.active_test_module
|
135
135
|
|
136
136
|
return unless test_session && test_module
|
137
137
|
|
@@ -150,7 +150,7 @@ module Datadog
|
|
150
150
|
def start_test_suite(test_suite_name)
|
151
151
|
finish_current_test_suite
|
152
152
|
|
153
|
-
@current_test_suite =
|
153
|
+
@current_test_suite = test_visibility_component.start_test_suite(test_suite_name)
|
154
154
|
end
|
155
155
|
|
156
156
|
def finish_current_test_suite
|
@@ -197,6 +197,10 @@ module Datadog
|
|
197
197
|
def configuration
|
198
198
|
Datadog.configuration.ci[:cucumber]
|
199
199
|
end
|
200
|
+
|
201
|
+
def test_visibility_component
|
202
|
+
Datadog.send(:components).test_visibility
|
203
|
+
end
|
200
204
|
end
|
201
205
|
end
|
202
206
|
end
|
@@ -19,7 +19,7 @@ module Datadog
|
|
19
19
|
|
20
20
|
test_suite_name = Helpers.test_suite_name(self, method)
|
21
21
|
|
22
|
-
test_suite =
|
22
|
+
test_suite = test_visibility_component.start_test_suite(test_suite_name)
|
23
23
|
|
24
24
|
results = super
|
25
25
|
return results unless test_suite
|
@@ -34,6 +34,10 @@ module Datadog
|
|
34
34
|
def datadog_configuration
|
35
35
|
Datadog.configuration.ci[:minitest]
|
36
36
|
end
|
37
|
+
|
38
|
+
def test_visibility_component
|
39
|
+
Datadog.send(:components).test_visibility
|
40
|
+
end
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
@@ -18,14 +18,14 @@ module Datadog
|
|
18
18
|
|
19
19
|
return unless datadog_configuration[:enabled]
|
20
20
|
|
21
|
-
|
21
|
+
test_visibility_component.start_test_session(
|
22
22
|
tags: {
|
23
23
|
CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
|
24
24
|
CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Minitest::Integration.version.to_s
|
25
25
|
},
|
26
26
|
service: datadog_configuration[:service_name]
|
27
27
|
)
|
28
|
-
|
28
|
+
test_visibility_component.start_test_module(Ext::FRAMEWORK)
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
@@ -33,6 +33,10 @@ module Datadog
|
|
33
33
|
def datadog_configuration
|
34
34
|
Datadog.configuration.ci[:minitest]
|
35
35
|
end
|
36
|
+
|
37
|
+
def test_visibility_component
|
38
|
+
Datadog.send(:components).test_visibility
|
39
|
+
end
|
36
40
|
end
|
37
41
|
end
|
38
42
|
end
|
@@ -26,12 +26,12 @@ module Datadog
|
|
26
26
|
test_suite_name = "#{test_suite_name} (#{name} concurrently)"
|
27
27
|
|
28
28
|
# for parallel execution we need to start a new test suite for each test
|
29
|
-
|
29
|
+
test_visibility_component.start_test_suite(test_suite_name)
|
30
30
|
end
|
31
31
|
|
32
32
|
source_file, line_number = method(name).source_location
|
33
33
|
|
34
|
-
test_span =
|
34
|
+
test_span = test_visibility_component.trace_test(
|
35
35
|
name,
|
36
36
|
test_suite_name,
|
37
37
|
tags: {
|
@@ -47,7 +47,7 @@ module Datadog
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def after_teardown
|
50
|
-
test_span =
|
50
|
+
test_span = test_visibility_component.active_test
|
51
51
|
return super unless test_span
|
52
52
|
|
53
53
|
finish_with_result(test_span, result_code)
|
@@ -77,6 +77,10 @@ module Datadog
|
|
77
77
|
def datadog_configuration
|
78
78
|
Datadog.configuration.ci[:minitest]
|
79
79
|
end
|
80
|
+
|
81
|
+
def test_visibility_component
|
82
|
+
Datadog.send(:components).test_visibility
|
83
|
+
end
|
80
84
|
end
|
81
85
|
|
82
86
|
module ClassMethods
|
@@ -34,10 +34,10 @@ module Datadog
|
|
34
34
|
|
35
35
|
if ci_queue?
|
36
36
|
suite_name = "#{suite_name} (ci-queue running example [#{test_name}])"
|
37
|
-
test_suite_span =
|
37
|
+
test_suite_span = test_visibility_component.start_test_suite(suite_name)
|
38
38
|
end
|
39
39
|
|
40
|
-
|
40
|
+
test_visibility_component.trace_test(
|
41
41
|
test_name,
|
42
42
|
suite_name,
|
43
43
|
tags: {
|
@@ -99,6 +99,10 @@ module Datadog
|
|
99
99
|
Datadog.configuration.ci[:rspec]
|
100
100
|
end
|
101
101
|
|
102
|
+
def test_visibility_component
|
103
|
+
Datadog.send(:components).test_visibility
|
104
|
+
end
|
105
|
+
|
102
106
|
def ci_queue?
|
103
107
|
!!defined?(::RSpec::Queue::ExampleExtension) &&
|
104
108
|
self.class.ancestors.include?(::RSpec::Queue::ExampleExtension)
|
@@ -21,7 +21,7 @@ module Datadog
|
|
21
21
|
return super unless top_level?
|
22
22
|
|
23
23
|
suite_name = "#{description} at #{file_path}"
|
24
|
-
test_suite =
|
24
|
+
test_suite = test_visibility_component.start_test_suite(suite_name)
|
25
25
|
|
26
26
|
success = super
|
27
27
|
return success unless test_suite
|
@@ -44,6 +44,10 @@ module Datadog
|
|
44
44
|
def datadog_configuration
|
45
45
|
Datadog.configuration.ci[:rspec]
|
46
46
|
end
|
47
|
+
|
48
|
+
def test_visibility_component
|
49
|
+
Datadog.send(:components).test_visibility
|
50
|
+
end
|
47
51
|
end
|
48
52
|
end
|
49
53
|
end
|
@@ -18,7 +18,7 @@ module Datadog
|
|
18
18
|
return super if ::RSpec.configuration.dry_run?
|
19
19
|
return super unless datadog_configuration[:enabled]
|
20
20
|
|
21
|
-
test_session =
|
21
|
+
test_session = test_visibility_component.start_test_session(
|
22
22
|
tags: {
|
23
23
|
CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
|
24
24
|
CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::RSpec::Integration.version.to_s
|
@@ -26,7 +26,7 @@ module Datadog
|
|
26
26
|
service: datadog_configuration[:service_name]
|
27
27
|
)
|
28
28
|
|
29
|
-
test_module =
|
29
|
+
test_module = test_visibility_component.start_test_module(Ext::FRAMEWORK)
|
30
30
|
|
31
31
|
result = super
|
32
32
|
return result unless test_module && test_session
|
@@ -49,6 +49,10 @@ module Datadog
|
|
49
49
|
def datadog_configuration
|
50
50
|
Datadog.configuration.ci[:rspec]
|
51
51
|
end
|
52
|
+
|
53
|
+
def test_visibility_component
|
54
|
+
Datadog.send(:components).test_visibility
|
55
|
+
end
|
52
56
|
end
|
53
57
|
end
|
54
58
|
end
|
@@ -18,7 +18,7 @@ module Datadog
|
|
18
18
|
return super if ::RSpec.configuration.dry_run?
|
19
19
|
return super unless datadog_configuration[:enabled]
|
20
20
|
|
21
|
-
test_session =
|
21
|
+
test_session = test_visibility_component.start_test_session(
|
22
22
|
tags: {
|
23
23
|
CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
|
24
24
|
CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::RSpec::Integration.version.to_s
|
@@ -26,7 +26,7 @@ module Datadog
|
|
26
26
|
service: datadog_configuration[:service_name]
|
27
27
|
)
|
28
28
|
|
29
|
-
test_module =
|
29
|
+
test_module = test_visibility_component.start_test_module(Ext::FRAMEWORK)
|
30
30
|
|
31
31
|
result = super
|
32
32
|
return result unless test_module && test_session
|
@@ -49,6 +49,10 @@ module Datadog
|
|
49
49
|
def datadog_configuration
|
50
50
|
Datadog.configuration.ci[:rspec]
|
51
51
|
end
|
52
|
+
|
53
|
+
def test_visibility_component
|
54
|
+
Datadog.send(:components).test_visibility
|
55
|
+
end
|
52
56
|
end
|
53
57
|
end
|
54
58
|
end
|
@@ -23,6 +23,23 @@ module Datadog
|
|
23
23
|
TAG_NODE_NAME = "ci.node.name"
|
24
24
|
TAG_CI_ENV_VARS = "_dd.ci.env_vars"
|
25
25
|
|
26
|
+
module Provider
|
27
|
+
APPVEYOR = "appveyor"
|
28
|
+
AZURE = "azurepipelines"
|
29
|
+
AWS = "awscodepipeline"
|
30
|
+
BITBUCKET = "bitbucket"
|
31
|
+
BITRISE = "bitrise"
|
32
|
+
BUDDYCI = "buddy"
|
33
|
+
BUILDKITE = "buildkite"
|
34
|
+
CIRCLECI = "circleci"
|
35
|
+
CODEFRESH = "codefresh"
|
36
|
+
GITHUB = "github"
|
37
|
+
GITLAB = "gitlab"
|
38
|
+
JENKINS = "jenkins"
|
39
|
+
TEAMCITY = "teamcity"
|
40
|
+
TRAVISCI = "travisci"
|
41
|
+
end
|
42
|
+
|
26
43
|
POSSIBLE_BUNDLE_LOCATIONS = %w[vendor/bundle .bundle].freeze
|
27
44
|
|
28
45
|
module_function
|