julewire-core 1.0.0 → 1.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/docs/advanced-configuration.md +2 -1
- data/docs/configuration.md +14 -7
- data/docs/context-and-propagation.md +26 -4
- data/docs/contracts.md +25 -50
- data/docs/extensions-and-api.md +87 -88
- data/docs/health-schema.md +12 -2
- data/docs/internals.md +24 -23
- data/docs/outputs-and-lifecycle.md +15 -8
- data/docs/records-and-data-policy.md +38 -7
- data/lib/julewire/core/cli/doctor.rb +20 -16
- data/lib/julewire/core/cli/line_helpers.rb +23 -12
- data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +3 -3
- data/lib/julewire/core/cli/log_formats/record_decoder.rb +1 -1
- data/lib/julewire/core/cli/log_formats.rb +9 -9
- data/lib/julewire/core/cli/tail.rb +19 -14
- data/lib/julewire/core/cli/transcode.rb +4 -5
- data/lib/julewire/core/cli.rb +1 -1
- data/lib/julewire/core/configuration.rb +1 -1
- data/lib/julewire/core/context_store.rb +56 -87
- data/lib/julewire/core/destinations/chaos_output.rb +2 -2
- data/lib/julewire/core/destinations/collection.rb +8 -16
- data/lib/julewire/core/destinations/definition.rb +1 -9
- data/lib/julewire/core/destinations/destination.rb +16 -66
- data/lib/julewire/core/destinations/processor_handling.rb +78 -0
- data/lib/julewire/core/destinations/registry.rb +1 -1
- data/lib/julewire/core/destinations/sink.rb +0 -1
- data/lib/julewire/core/destinations/synchronized_output.rb +54 -17
- data/lib/julewire/core/destinations/tail_sampling.rb +18 -27
- data/lib/julewire/core/destinations/write_step.rb +5 -5
- data/lib/julewire/core/destinations.rb +0 -7
- data/lib/julewire/core/diagnostics/callback_notifier.rb +4 -2
- data/lib/julewire/core/diagnostics/doctor.rb +22 -15
- data/lib/julewire/core/diagnostics/failure_snapshot.rb +9 -3
- data/lib/julewire/core/diagnostics/health.rb +41 -57
- data/lib/julewire/core/diagnostics/integration_health_store.rb +11 -24
- data/lib/julewire/core/diagnostics/internal_records.rb +8 -12
- data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +16 -55
- data/lib/julewire/core/diagnostics/meta_observer.rb +37 -44
- data/lib/julewire/core/diagnostics/process_integration_health.rb +0 -5
- data/lib/julewire/core/diagnostics/tail/renderer.rb +1 -1
- data/lib/julewire/core/diagnostics/tail.rb +18 -22
- data/lib/julewire/core/execution/boundary.rb +2 -8
- data/lib/julewire/core/execution/handle.rb +14 -17
- data/lib/julewire/core/execution/lineage.rb +26 -62
- data/lib/julewire/core/execution/measurement_handle.rb +6 -10
- data/lib/julewire/core/execution/scope.rb +17 -22
- data/lib/julewire/core/execution/scope_fields.rb +4 -13
- data/lib/julewire/core/execution/scope_identity.rb +1 -9
- data/lib/julewire/core/execution/scope_snapshot.rb +18 -39
- data/lib/julewire/core/execution/summary_state.rb +16 -19
- data/lib/julewire/core/execution/view.rb +8 -17
- data/lib/julewire/core/facade_methods.rb +67 -65
- data/lib/julewire/core/fields/attribute_keys.rb +0 -2
- data/lib/julewire/core/fields/bags.rb +23 -27
- data/lib/julewire/core/fields/field_set.rb +52 -16
- data/lib/julewire/core/fields/field_stack.rb +69 -109
- data/lib/julewire/core/fields/internal/deletion.rb +6 -9
- data/lib/julewire/core/fields/internal.rb +35 -26
- data/lib/julewire/core/fields/lookup.rb +9 -13
- data/lib/julewire/core/fields/section_proxy.rb +1 -9
- data/lib/julewire/core/fields/stack_set.rb +9 -14
- data/lib/julewire/core/fields/static_labels.rb +1 -1
- data/lib/julewire/core/integration/configurable.rb +4 -3
- data/lib/julewire/core/integration/destination_health.rb +28 -7
- data/lib/julewire/core/integration/event_subscriber.rb +1 -1
- data/lib/julewire/core/integration/facade.rb +12 -15
- data/lib/julewire/core/integration/fork_hooks.rb +12 -36
- data/lib/julewire/core/integration/health.rb +0 -3
- data/lib/julewire/core/integration/ivar_state.rb +0 -5
- data/lib/julewire/core/integration/protocol.rb +20 -0
- data/lib/julewire/core/integration/settings.rb +4 -4
- data/lib/julewire/core/integration/subscriber_install.rb +2 -2
- data/lib/julewire/core/integration/subscription.rb +3 -2
- data/lib/julewire/core/integration/values.rb +0 -17
- data/lib/julewire/core/local_storage.rb +3 -19
- data/lib/julewire/core/processing/invalid_result_failure.rb +23 -0
- data/lib/julewire/core/processing/level_threshold.rb +7 -14
- data/lib/julewire/core/processing/match.rb +6 -4
- data/lib/julewire/core/processing/pipeline.rb +46 -40
- data/lib/julewire/core/processing/processor_chain.rb +22 -11
- data/lib/julewire/core/processing/processor_registry.rb +3 -4
- data/lib/julewire/core/processing/record_field_transform.rb +0 -4
- data/lib/julewire/core/processing/sampling.rb +1 -3
- data/lib/julewire/core/processing.rb +2 -2
- data/lib/julewire/core/propagation/carrier.rb +73 -19
- data/lib/julewire/core/propagation.rb +18 -4
- data/lib/julewire/core/records/build_input.rb +75 -0
- data/lib/julewire/core/records/console_formatter.rb +6 -6
- data/lib/julewire/core/records/deconstruct.rb +1 -1
- data/lib/julewire/core/records/display_message.rb +2 -4
- data/lib/julewire/core/records/draft.rb +127 -258
- data/lib/julewire/core/records/lazy_emit_input.rb +2 -12
- data/lib/julewire/core/records/metadata.rb +4 -3
- data/lib/julewire/core/records/public_projection.rb +2 -2
- data/lib/julewire/core/records/raw_input.rb +1 -1
- data/lib/julewire/core/records/record.rb +30 -18
- data/lib/julewire/core/records/severity.rb +7 -15
- data/lib/julewire/core/runtime.rb +87 -72
- data/lib/julewire/core/runtime_registry.rb +20 -15
- data/lib/julewire/core/runtime_state.rb +5 -3
- data/lib/julewire/core/scheduling/shared_scheduler.rb +28 -22
- data/lib/julewire/core/serialization/backtrace_limiter.rb +13 -9
- data/lib/julewire/core/serialization/bounded_transform.rb +3 -2
- data/lib/julewire/core/serialization/bounded_traversal.rb +72 -83
- data/lib/julewire/core/serialization/deep_compact_empty.rb +9 -11
- data/lib/julewire/core/serialization/deep_freeze.rb +45 -6
- data/lib/julewire/core/serialization/encoding_sanitizer.rb +2 -3
- data/lib/julewire/core/serialization/exception_shape.rb +7 -6
- data/lib/julewire/core/serialization/json_encoder.rb +10 -13
- data/lib/julewire/core/serialization/serializer.rb +22 -49
- data/lib/julewire/core/serialization/serializer_pool.rb +9 -0
- data/lib/julewire/core/serialization/text_encoder.rb +5 -5
- data/lib/julewire/core/serialization/truncation_metadata.rb +157 -0
- data/lib/julewire/core/serialization/value_copy.rb +183 -109
- data/lib/julewire/core/serialization/value_traversal.rb +10 -127
- data/lib/julewire/core/testing.rb +6 -56
- data/lib/julewire/core/validation.rb +2 -3
- data/lib/julewire/core/version.rb +1 -1
- data/lib/julewire/core.rb +2 -7
- metadata +7 -19
- data/lib/julewire/core/scheduling/deadline_scheduler.rb +0 -207
- data/lib/julewire/core/testing/chaos/catalog.rb +0 -72
- data/lib/julewire/core/testing/chaos/core_runtime.rb +0 -120
- data/lib/julewire/core/testing/chaos/destination.rb +0 -55
- data/lib/julewire/core/testing/chaos/emitter.rb +0 -20
- data/lib/julewire/core/testing/chaos/raising_output.rb +0 -42
- data/lib/julewire/core/testing/chaos.rb +0 -80
- data/lib/julewire/core/testing/contracts/component.rb +0 -162
- data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +0 -59
- data/lib/julewire/core/testing/contracts/integration.rb +0 -166
- data/lib/julewire/core/testing/contracts/integration_fields.rb +0 -36
- data/lib/julewire/core/testing/contracts/record_draft.rb +0 -37
- data/lib/julewire/core/testing/contracts/runtime.rb +0 -178
- data/lib/julewire/core/testing/contracts/wire.rb +0 -60
- data/lib/julewire/core/testing/contracts.rb +0 -24
- data/lib/julewire/core/testing/coverage.rb +0 -58
- data/lib/julewire/core/testing/test_reports.rb +0 -78
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
|
-
module Julewire
|
|
6
|
-
module Core
|
|
7
|
-
module Testing
|
|
8
|
-
module Contracts
|
|
9
|
-
module Component
|
|
10
|
-
def assert_julewire_processor_contract(processor, draft: build_julewire_contract_draft)
|
|
11
|
-
assert_respond_to processor, :call
|
|
12
|
-
|
|
13
|
-
result = processor.call(draft)
|
|
14
|
-
return :drop if result == :drop
|
|
15
|
-
|
|
16
|
-
result = draft unless result.is_a?(Julewire::Core::Records::Draft)
|
|
17
|
-
result.to_record
|
|
18
|
-
result
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def assert_julewire_formatter_contract(formatter, record: build_julewire_contract_record)
|
|
22
|
-
assert_respond_to formatter, :call
|
|
23
|
-
|
|
24
|
-
formatted = formatter.call(record)
|
|
25
|
-
encoded = Julewire::Core::Serialization::JsonEncoder.new.call(formatted)
|
|
26
|
-
|
|
27
|
-
assert_kind_of String, encoded
|
|
28
|
-
formatted
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def assert_julewire_destination_contract(destination, record: build_julewire_contract_record)
|
|
32
|
-
%i[name emit flush close health].each do |method_name|
|
|
33
|
-
assert_respond_to destination, method_name
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
assert_nil destination.emit(record)
|
|
37
|
-
assert destination.flush(timeout: 0)
|
|
38
|
-
assert destination.close(timeout: 0)
|
|
39
|
-
assert_kind_of Hash, destination.health
|
|
40
|
-
destination
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def assert_julewire_record_shape_contract(record: build_julewire_shape_contract_record)
|
|
44
|
-
Julewire::Core::Records::Record.validate_normalized!(record)
|
|
45
|
-
|
|
46
|
-
data = record.to_h
|
|
47
|
-
assert_julewire_record_data_shape!(record, data)
|
|
48
|
-
assert_julewire_record_formatter_shape!(record)
|
|
49
|
-
assert_julewire_record_serializer_shape!(record)
|
|
50
|
-
|
|
51
|
-
record
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def build_julewire_contract_record(fields = {})
|
|
55
|
-
build_julewire_contract_draft(fields).to_record
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def build_julewire_contract_draft(fields = {})
|
|
59
|
-
Julewire::Core::Records::Draft.build(
|
|
60
|
-
{
|
|
61
|
-
severity: :info,
|
|
62
|
-
kind: :point,
|
|
63
|
-
event: "test.event",
|
|
64
|
-
source: "test",
|
|
65
|
-
message: "test message",
|
|
66
|
-
attributes: { "test.attribute" => "value" },
|
|
67
|
-
payload: { value: 1 }
|
|
68
|
-
}.merge(fields),
|
|
69
|
-
context: {},
|
|
70
|
-
scope: nil,
|
|
71
|
-
freeze_sections: false
|
|
72
|
-
)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
private
|
|
76
|
-
|
|
77
|
-
def assert_julewire_record_data_shape!(record, data)
|
|
78
|
-
assert_julewire_symbol_keys!(data)
|
|
79
|
-
assert_equal(
|
|
80
|
-
Julewire::Core::Records::Record::REQUIRED_KEYS,
|
|
81
|
-
data.keys & Julewire::Core::Records::Record::REQUIRED_KEYS
|
|
82
|
-
)
|
|
83
|
-
assert record.frozen?
|
|
84
|
-
assert record.serializable_data.frozen?
|
|
85
|
-
refute data.fetch(:execution).key?(:ancestors)
|
|
86
|
-
refute data.fetch(:execution).key?(:ancestors_truncated)
|
|
87
|
-
assert_equal [{ type: :request, id: "root-1" }], record.lineage.ancestors
|
|
88
|
-
assert_equal "root-1", record.lineage.root_reference[:id]
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def assert_julewire_record_formatter_shape!(record)
|
|
92
|
-
formatted = Julewire::Core::Serialization::Serializer.call(
|
|
93
|
-
Julewire::Core::Records::Formatter.new.call(record),
|
|
94
|
-
compact_empty: true
|
|
95
|
-
)
|
|
96
|
-
refute formatted.key?("carry")
|
|
97
|
-
assert_equal "visible", formatted.dig("execution", "custom")
|
|
98
|
-
%w[root parent ancestors depth ancestors_truncated].each do |key|
|
|
99
|
-
refute formatted.fetch("execution").key?(key)
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def assert_julewire_record_serializer_shape!(record)
|
|
104
|
-
serialized = Julewire::Core::Serialization::Serializer.call(record)
|
|
105
|
-
assert_equal(
|
|
106
|
-
Julewire::Core::Serialization::ValueCopy::CIRCULAR_REFERENCE,
|
|
107
|
-
serialized.dig("payload", "cycle", "self")
|
|
108
|
-
)
|
|
109
|
-
assert_equal Julewire::Core::Serialization::Serializer::NAN_VALUE, serialized.dig("payload", "nan")
|
|
110
|
-
assert_equal "1.25", serialized.dig("payload", "decimal")
|
|
111
|
-
JSON.generate(serialized, allow_nan: false)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def build_julewire_shape_contract_record
|
|
115
|
-
cycle = {}
|
|
116
|
-
cycle[:self] = cycle
|
|
117
|
-
|
|
118
|
-
build_julewire_contract_record(
|
|
119
|
-
execution: {
|
|
120
|
-
type: :request,
|
|
121
|
-
id: "execution-1",
|
|
122
|
-
root: { type: :request, id: "root-1" },
|
|
123
|
-
parent: { type: :job, id: "parent-1" },
|
|
124
|
-
ancestors: [{ type: :request, id: "root-1" }],
|
|
125
|
-
ancestors_truncated: false,
|
|
126
|
-
depth: 2,
|
|
127
|
-
custom: "visible"
|
|
128
|
-
},
|
|
129
|
-
context: { request_id: "request-1" },
|
|
130
|
-
neutral: Fields::AttributeKeys.fields("http.request.method" => "GET"),
|
|
131
|
-
carry: { http: { request_headers: { traceparent: contract_traceparent } } },
|
|
132
|
-
payload: {
|
|
133
|
-
value: 1,
|
|
134
|
-
nested: { string_key: true },
|
|
135
|
-
cycle: cycle,
|
|
136
|
-
nan: Float::NAN,
|
|
137
|
-
decimal: contract_decimal
|
|
138
|
-
}
|
|
139
|
-
)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def contract_decimal
|
|
143
|
-
defined?(BigDecimal) ? BigDecimal("1.25") : "1.25"
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def assert_julewire_symbol_keys!(value)
|
|
147
|
-
case value
|
|
148
|
-
when Hash
|
|
149
|
-
assert(
|
|
150
|
-
value.keys.all?(Symbol),
|
|
151
|
-
"expected only symbol keys in #{value.inspect}"
|
|
152
|
-
)
|
|
153
|
-
value.each_value { assert_julewire_symbol_keys!(it) }
|
|
154
|
-
when Array
|
|
155
|
-
value.each { assert_julewire_symbol_keys!(it) }
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
end
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "timeout"
|
|
4
|
-
|
|
5
|
-
module Julewire
|
|
6
|
-
module Core
|
|
7
|
-
module Testing
|
|
8
|
-
module Contracts
|
|
9
|
-
module DeadlineScheduler
|
|
10
|
-
def assert_julewire_deadline_scheduler_spi_contract
|
|
11
|
-
scheduler = Julewire::Core::Scheduling::DeadlineScheduler.new(thread_name: "julewire-contract-deadline")
|
|
12
|
-
called = false
|
|
13
|
-
|
|
14
|
-
result = scheduler.schedule(0) { called = true }
|
|
15
|
-
|
|
16
|
-
assert called
|
|
17
|
-
assert_nil result
|
|
18
|
-
assert_nil scheduler.cancel(nil)
|
|
19
|
-
assert_scheduler_runs_callbacks(scheduler)
|
|
20
|
-
assert_scheduler_cancel_suppresses_callback(scheduler)
|
|
21
|
-
assert_scheduler_after_fork_resets_pending_callbacks(scheduler)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
private
|
|
25
|
-
|
|
26
|
-
def assert_scheduler_runs_callbacks(scheduler)
|
|
27
|
-
queue = Queue.new
|
|
28
|
-
|
|
29
|
-
scheduler.schedule(0.001) { queue << :done }
|
|
30
|
-
|
|
31
|
-
assert_equal :done, Timeout.timeout(1) { queue.pop }
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def assert_scheduler_cancel_suppresses_callback(scheduler)
|
|
35
|
-
queue = Queue.new
|
|
36
|
-
token = scheduler.schedule(0.01) { queue << :cancelled }
|
|
37
|
-
|
|
38
|
-
scheduler.cancel(token)
|
|
39
|
-
scheduler.schedule(0.02) { queue << :sentinel }
|
|
40
|
-
|
|
41
|
-
assert_equal :sentinel, Timeout.timeout(1) { queue.pop }
|
|
42
|
-
assert_empty Julewire::Core::Testing.nonblocking_queue_values(queue)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def assert_scheduler_after_fork_resets_pending_callbacks(scheduler)
|
|
46
|
-
queue = Queue.new
|
|
47
|
-
|
|
48
|
-
scheduler.schedule(0.01) { queue << :old }
|
|
49
|
-
assert_same scheduler, scheduler.after_fork!
|
|
50
|
-
scheduler.schedule(0.001) { queue << :new }
|
|
51
|
-
|
|
52
|
-
assert_equal :new, Timeout.timeout(1) { queue.pop }
|
|
53
|
-
assert_empty Julewire::Core::Testing.nonblocking_queue_values(queue)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "integration_fields"
|
|
4
|
-
|
|
5
|
-
module Julewire
|
|
6
|
-
module Core
|
|
7
|
-
module Testing
|
|
8
|
-
module Contracts
|
|
9
|
-
module Integration
|
|
10
|
-
include IntegrationFields
|
|
11
|
-
|
|
12
|
-
def assert_julewire_integration_spi_contract
|
|
13
|
-
assert_julewire_integration_health_contract
|
|
14
|
-
assert_julewire_integration_field_overlay_contract
|
|
15
|
-
assert_julewire_integration_timestamp_contract
|
|
16
|
-
assert_julewire_integration_payload_contract
|
|
17
|
-
assert_julewire_integration_value_contract
|
|
18
|
-
assert_julewire_deadline_scheduler_spi_contract
|
|
19
|
-
assert_julewire_integration_ivar_state_contract
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def assert_julewire_validation_spi_contract
|
|
23
|
-
validation = Julewire::Core::Validation
|
|
24
|
-
assert_nil validation.validate_options!({ known: true }, %i[known], name: :contract)
|
|
25
|
-
assert_equal 1, validation.validate_byte_limit!(1, name: :limit)
|
|
26
|
-
assert_equal 0, validation.validate_integer_limit!(0, name: :count)
|
|
27
|
-
|
|
28
|
-
error = assert_raises(ArgumentError) do
|
|
29
|
-
validation.validate_options!({ unknown: true }, %i[known], name: :contract)
|
|
30
|
-
end
|
|
31
|
-
assert_match "unknown contract options: unknown", error.message
|
|
32
|
-
|
|
33
|
-
error = assert_raises(ArgumentError) do
|
|
34
|
-
validation.validate_byte_limit!(0, name: :limit)
|
|
35
|
-
end
|
|
36
|
-
assert_match "limit must be nil or a positive Integer", error.message
|
|
37
|
-
|
|
38
|
-
error = assert_raises(ArgumentError) do
|
|
39
|
-
validation.validate_integer_limit!(-1, name: :count)
|
|
40
|
-
end
|
|
41
|
-
assert_match "count must be a non-negative Integer", error.message
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def assert_julewire_truncation_marker_spi_contract
|
|
45
|
-
assert_equal "[MaxDepth]", Julewire::Core::Serialization::Serializer::MAX_DEPTH_VALUE
|
|
46
|
-
assert_equal "...[Truncated]", Julewire::Core::Serialization::Serializer::TRUNCATED_SUFFIX
|
|
47
|
-
assert_equal "_julewire_truncation", Julewire::Core::Serialization::Serializer::TRUNCATION_METADATA_KEY
|
|
48
|
-
assert_equal(
|
|
49
|
-
{
|
|
50
|
-
"truncated" => true,
|
|
51
|
-
"truncated_fields" => ["array_items"],
|
|
52
|
-
"limits" => {
|
|
53
|
-
"max_array_items" => 1,
|
|
54
|
-
"max_depth" => 8,
|
|
55
|
-
"max_hash_keys" => 1_000,
|
|
56
|
-
"max_string_bytes" => 3
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
Julewire::Core::Serialization::Serializer.truncation_metadata(
|
|
60
|
-
["array_items"],
|
|
61
|
-
max_array_items: 1,
|
|
62
|
-
max_string_bytes: 3
|
|
63
|
-
)
|
|
64
|
-
)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def assert_julewire_bounded_transform_spi_contract
|
|
68
|
-
marker_key = Julewire::Core::Serialization::Serializer::TRUNCATION_METADATA_KEY.to_sym
|
|
69
|
-
result = Julewire::Core::Serialization::BoundedTransform.call(
|
|
70
|
-
{ secret: "value", list: [1, 2], long: "abcdef" },
|
|
71
|
-
max_array_items: 1,
|
|
72
|
-
max_string_bytes: 3
|
|
73
|
-
) do |_value, key:, **|
|
|
74
|
-
key == :secret ? "[FILTERED]" : Julewire::Core::Serialization::BoundedTransform::CONTINUE
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
assert_equal "[FI...[Truncated]", result.fetch(:secret)
|
|
78
|
-
assert_equal "abc...[Truncated]", result.fetch(:long)
|
|
79
|
-
assert_equal ["array_items"], result.dig(:list, 1, marker_key, "truncated_fields")
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def assert_julewire_integration_failure_contract(integration:, component:, exercise:)
|
|
83
|
-
assert_nil exercise.call
|
|
84
|
-
|
|
85
|
-
health = Julewire.health
|
|
86
|
-
integration_health = health.dig(:process_integrations, integration.to_sym)
|
|
87
|
-
|
|
88
|
-
assert_equal :degraded, health.fetch(:status)
|
|
89
|
-
assert_kind_of Hash, integration_health
|
|
90
|
-
assert_equal :degraded, integration_health.fetch(:status)
|
|
91
|
-
assert_equal 1, integration_health.dig(:counts, :failures)
|
|
92
|
-
assert_equal component.to_sym, integration_health.dig(:last_failure, :component)
|
|
93
|
-
refute_includes integration_health.fetch(:last_failure), :message
|
|
94
|
-
|
|
95
|
-
[health, integration_health]
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def assert_julewire_integration_health_contract
|
|
99
|
-
Julewire::Core::Diagnostics::ProcessIntegrationHealth.reset!
|
|
100
|
-
Julewire::Core::Integration::Health.record_failure(
|
|
101
|
-
:contract,
|
|
102
|
-
RuntimeError.new("secret"),
|
|
103
|
-
component: :subscriber
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
degraded = Julewire::Core::Diagnostics::ProcessIntegrationHealth.health.fetch(:contract)
|
|
107
|
-
assert_equal :degraded, degraded.fetch(:status)
|
|
108
|
-
assert_equal 1, degraded.dig(:counts, :failures)
|
|
109
|
-
refute_includes degraded.fetch(:last_failure), :message
|
|
110
|
-
|
|
111
|
-
Julewire::Core::Integration::Health.record_success(:contract)
|
|
112
|
-
recovered = Julewire::Core::Diagnostics::ProcessIntegrationHealth.health.fetch(:contract)
|
|
113
|
-
assert_equal :ok, recovered.fetch(:status)
|
|
114
|
-
assert_equal 1, recovered.dig(:counts, :failures)
|
|
115
|
-
assert_equal "RuntimeError", recovered.dig(:last_failure, :class)
|
|
116
|
-
ensure
|
|
117
|
-
Julewire::Core::Diagnostics::ProcessIntegrationHealth.reset!
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def assert_julewire_integration_timestamp_contract
|
|
121
|
-
now = Time.utc(2026, 5, 30, 12, 0, 0, 123_456)
|
|
122
|
-
values = Julewire::Core::Integration::Values::Shape
|
|
123
|
-
|
|
124
|
-
assert_equal "2026-05-30T12:00:00.123456000Z", values.timestamp(now)
|
|
125
|
-
assert_equal "1970-01-01T00:00:01.000000002Z", values.timestamp(1_000_000_002)
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def assert_julewire_integration_payload_contract
|
|
129
|
-
values = Julewire::Core::Integration::Values::Shape
|
|
130
|
-
|
|
131
|
-
assert_equal({ account_id: "acct-1" }, values.payload_hash("account_id" => "acct-1"))
|
|
132
|
-
assert_equal(
|
|
133
|
-
{ Julewire::Core::Fields::FieldSet::VALUE_KEY => "raw" },
|
|
134
|
-
values.payload_hash("raw")
|
|
135
|
-
)
|
|
136
|
-
assert_equal({ request_id: "req-1" }, values.hash_or_empty("request_id" => "req-1"))
|
|
137
|
-
assert_equal({}, values.hash_or_empty("raw"))
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def assert_julewire_integration_value_contract
|
|
141
|
-
values = Julewire::Core::Integration::Values::Read
|
|
142
|
-
|
|
143
|
-
assert_equal [true, false], [values.blank?(""), values.blank?("value")]
|
|
144
|
-
assert_equal "symbol", values.value({ key: "symbol" }, :key)
|
|
145
|
-
assert_equal "string", values.value({ "key" => "string" }, :key)
|
|
146
|
-
assert_equal "method", values.value(Class.new { def key = "method" }.new, :key)
|
|
147
|
-
assert_equal(
|
|
148
|
-
"nested",
|
|
149
|
-
values.nested_value({ outer: { "inner" => "nested" } }, :outer, :inner)
|
|
150
|
-
)
|
|
151
|
-
assert_equal "path", values.path_value({ outer: { "inner" => "path" } }, %i[outer inner])
|
|
152
|
-
assert_equal :fallback, values.value(Object.new, :missing, default: :fallback)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def assert_julewire_integration_ivar_state_contract
|
|
156
|
-
owner = Object.new
|
|
157
|
-
state = Julewire::Core::Integration::IvarState.new(:@julewire_contract_install)
|
|
158
|
-
assert_nil state.fetch(owner)
|
|
159
|
-
assert_equal :installed, state.store(owner, :installed)
|
|
160
|
-
assert_equal :installed, state.fetch(owner)
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Julewire
|
|
4
|
-
module Core
|
|
5
|
-
module Testing
|
|
6
|
-
module Contracts
|
|
7
|
-
module IntegrationFields
|
|
8
|
-
private
|
|
9
|
-
|
|
10
|
-
def assert_julewire_integration_field_overlay_contract
|
|
11
|
-
records = Julewire::Core::Testing.capture(snapshot: true) do
|
|
12
|
-
Julewire.with_execution(type: :contract, emit_summary: false) do
|
|
13
|
-
Julewire::Core::Integration::Facade.add_context(contract_context: "ctx")
|
|
14
|
-
Julewire::Core::Integration::Facade.add_carry(contract_carry: "carry")
|
|
15
|
-
Julewire::Core::Integration::Facade.add_attributes(contract_attribute: "attr")
|
|
16
|
-
Julewire::Core::Integration::Facade.add_neutral("contract.neutral": "neutral")
|
|
17
|
-
Julewire.emit(event: "contract.spi", source: "contract")
|
|
18
|
-
end
|
|
19
|
-
Julewire.flush
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
record = records.find { it[:event] == "contract.spi" }
|
|
23
|
-
flunk("expected integration field overlay contract record") unless record
|
|
24
|
-
|
|
25
|
-
assert_equal "ctx", record.dig(:context, :contract_context)
|
|
26
|
-
assert_equal "carry", record.dig(:carry, :contract_carry)
|
|
27
|
-
assert_equal "attr", record.dig(:attributes, :contract_attribute)
|
|
28
|
-
assert_equal "neutral", record.dig(:neutral, :"contract.neutral")
|
|
29
|
-
ensure
|
|
30
|
-
Julewire.reset!
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Julewire
|
|
4
|
-
module Core
|
|
5
|
-
module Testing
|
|
6
|
-
module Contracts
|
|
7
|
-
module RecordDraft
|
|
8
|
-
def assert_julewire_record_draft_transform_contract(draft: build_julewire_transform_contract_draft)
|
|
9
|
-
draft.transform_field!(:message) { |message| "#{message} transformed" }
|
|
10
|
-
draft.transform_section!(:payload) { it.merge(section_transformed: true) }
|
|
11
|
-
draft.transform_record! do |data|
|
|
12
|
-
data.merge(labels: data.fetch(:labels).merge(record_transformed: "yes"))
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
record = draft.to_record
|
|
16
|
-
assert_equal "test message transformed", record.fetch(:message)
|
|
17
|
-
assert record.dig(:payload, :section_transformed)
|
|
18
|
-
assert_equal "yes", record.dig(:labels, :record_transformed)
|
|
19
|
-
assert_equal [{ type: "request", id: "root-1" }], record.lineage.ancestors
|
|
20
|
-
draft
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def build_julewire_transform_contract_draft
|
|
24
|
-
build_julewire_contract_draft(
|
|
25
|
-
execution: {
|
|
26
|
-
type: "job",
|
|
27
|
-
id: "job-1",
|
|
28
|
-
ancestors: [{ type: "request", id: "root-1" }]
|
|
29
|
-
},
|
|
30
|
-
labels: { service: "contract" }
|
|
31
|
-
)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Julewire
|
|
4
|
-
module Core
|
|
5
|
-
module Testing
|
|
6
|
-
module Contracts
|
|
7
|
-
module Runtime
|
|
8
|
-
def assert_julewire_runtime_integration_contract(**options)
|
|
9
|
-
run_julewire_runtime_contract(options)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def assert_julewire_execution_boundary_contract(**options)
|
|
13
|
-
run_julewire_execution_boundary_contract(options)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def assert_julewire_failure_containment_contract(configure:, destination_name: :default, emit: nil)
|
|
17
|
-
Julewire.configure(&configure)
|
|
18
|
-
|
|
19
|
-
assert_nil((emit || default_failure_contract_emit).call)
|
|
20
|
-
|
|
21
|
-
health = Julewire.health
|
|
22
|
-
destination_health = health.dig(:pipeline, :destinations, destination_name.to_sym)
|
|
23
|
-
|
|
24
|
-
assert_equal :degraded, health.fetch(:status)
|
|
25
|
-
assert_kind_of Hash, destination_health
|
|
26
|
-
|
|
27
|
-
[health, destination_health]
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def assert_julewire_record_source_contract(
|
|
31
|
-
records:,
|
|
32
|
-
event:,
|
|
33
|
-
source:,
|
|
34
|
-
**options
|
|
35
|
-
)
|
|
36
|
-
record = find_contract_record(records, options.fetch(:event_path, %w[event]), event)
|
|
37
|
-
|
|
38
|
-
assert_equal source.to_s, fetch_contract_path(record, options.fetch(:source_path, %w[source])).to_s
|
|
39
|
-
assert_contract_optional_source_field(record, options, :logger)
|
|
40
|
-
assert_contract_optional_source_field(record, options, :kind)
|
|
41
|
-
|
|
42
|
-
record
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
def assert_contract_optional_source_field(record, options, field)
|
|
48
|
-
expected = options[field]
|
|
49
|
-
return unless expected
|
|
50
|
-
|
|
51
|
-
path = options.fetch(:"#{field}_path", [field.to_s])
|
|
52
|
-
assert_equal expected.to_s, fetch_contract_path(record, path).to_s
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def run_julewire_runtime_contract(options)
|
|
56
|
-
Julewire.configure(&options.fetch(:configure))
|
|
57
|
-
emit_runtime_contract_records
|
|
58
|
-
|
|
59
|
-
records = options.fetch(:records).call
|
|
60
|
-
point = find_contract_record(records, options.fetch(:event_path), point_event(options))
|
|
61
|
-
summary = find_contract_record(records, options.fetch(:event_path), summary_event(options))
|
|
62
|
-
health = Julewire.health
|
|
63
|
-
|
|
64
|
-
assert_runtime_contract_records(point, summary, health, options)
|
|
65
|
-
[point, summary, health]
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def run_julewire_execution_boundary_contract(options)
|
|
69
|
-
Julewire.configure(&options.fetch(:configure))
|
|
70
|
-
options.fetch(:exercise).call(**execution_boundary_probe(options))
|
|
71
|
-
Julewire.flush
|
|
72
|
-
|
|
73
|
-
records = options.fetch(:records).call
|
|
74
|
-
point = find_contract_record(records, options.fetch(:event_path), point_event(options))
|
|
75
|
-
summary = find_contract_record(records, options.fetch(:event_path), summary_event(options))
|
|
76
|
-
health = Julewire.health
|
|
77
|
-
|
|
78
|
-
assert_runtime_contract_records(point, summary, health, options)
|
|
79
|
-
[point, summary, health]
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def emit_runtime_contract_records
|
|
83
|
-
Julewire.with_execution(
|
|
84
|
-
type: :contract,
|
|
85
|
-
id: "contract-1",
|
|
86
|
-
summary_event: "contract.completed",
|
|
87
|
-
summary_source: "contract"
|
|
88
|
-
) do
|
|
89
|
-
Julewire.context.add(request_id: "request-1")
|
|
90
|
-
Julewire.carry.add(http: { request_headers: { traceparent: contract_traceparent } })
|
|
91
|
-
Julewire.summary.add(total: 2)
|
|
92
|
-
Julewire.emit(event: point_event({}), source: "contract", message: "point", payload: { value: 1 })
|
|
93
|
-
end
|
|
94
|
-
Julewire.flush
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def assert_runtime_contract_records(point, summary, health, options)
|
|
98
|
-
assert_equal "request-1", fetch_contract_path(point, options.fetch(:context_path) + [:request_id])
|
|
99
|
-
assert_equal 2, fetch_contract_path(summary, options.fetch(:summary_payload_path) + [:total])
|
|
100
|
-
assert_runtime_contract_carry(point, options[:carry_path]) if options[:carry_path]
|
|
101
|
-
|
|
102
|
-
assert health.dig(:pipeline, :configured)
|
|
103
|
-
assert_kind_of Hash, health.dig(:pipeline, :destinations, contract_destination_name(options))
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def assert_runtime_contract_carry(point, carry_path)
|
|
107
|
-
path = carry_path + %i[http request_headers traceparent]
|
|
108
|
-
|
|
109
|
-
assert_equal contract_traceparent, fetch_contract_path(point, path)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def contract_destination_name(options)
|
|
113
|
-
options.fetch(:destination_name, :default).to_sym
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def default_failure_contract_emit
|
|
117
|
-
lambda do
|
|
118
|
-
Julewire.emit(
|
|
119
|
-
event: "contract.failure",
|
|
120
|
-
source: "contract",
|
|
121
|
-
message: "failure probe",
|
|
122
|
-
payload: { token: "secret" }
|
|
123
|
-
)
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def execution_boundary_probe(options)
|
|
128
|
-
{
|
|
129
|
-
add_summary: -> { Julewire.summary.add(total: 2) },
|
|
130
|
-
carry: { http: { request_headers: { traceparent: contract_traceparent } } },
|
|
131
|
-
context: { request_id: "request-1" },
|
|
132
|
-
emit_point: lambda do
|
|
133
|
-
Julewire.emit(event: point_event(options), source: "contract", message: "point", payload: { value: 1 })
|
|
134
|
-
end,
|
|
135
|
-
summary_event: summary_event(options),
|
|
136
|
-
traceparent: contract_traceparent
|
|
137
|
-
}
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def point_event(options)
|
|
141
|
-
options.fetch(:point_event, "contract.point")
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def summary_event(options)
|
|
145
|
-
options.fetch(:summary_event, "contract.completed")
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def find_contract_record(records, event_path, event)
|
|
149
|
-
record = records.find { fetch_contract_path(it, event_path) == event }
|
|
150
|
-
return record if record
|
|
151
|
-
|
|
152
|
-
flunk("expected contract record #{event.inspect}, got #{records.inspect}")
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def fetch_contract_path(value, path)
|
|
156
|
-
path.reduce(value) { |current, key| fetch_contract_key(current, key) }
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def fetch_contract_key(value, key)
|
|
160
|
-
return value.fetch(key) if value.respond_to?(:key?) && value.key?(key)
|
|
161
|
-
return value.fetch(key.to_s) if value.respond_to?(:key?) && value.key?(key.to_s)
|
|
162
|
-
return fetch_symbol_contract_key(value, key) if key.respond_to?(:to_sym)
|
|
163
|
-
|
|
164
|
-
flunk("expected key #{key.inspect} in #{value.inspect}")
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
def fetch_symbol_contract_key(value, key)
|
|
168
|
-
return value.fetch(key.to_sym) if value.respond_to?(:key?) && value.key?(key.to_sym)
|
|
169
|
-
|
|
170
|
-
flunk("expected key #{key.inspect} in #{value.inspect}")
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def contract_traceparent = "00-06796866738c859f2f19b7cfb3214824-000000000000004a-01"
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
end
|