familia 2.0.0.pre6 → 2.0.0.pre7
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/.github/workflows/claude-code-review.yml +57 -0
- data/.github/workflows/claude.yml +71 -0
- data/.gitignore +5 -1
- data/.rubocop.yml +3 -0
- data/CLAUDE.md +32 -13
- data/Gemfile +2 -2
- data/Gemfile.lock +2 -2
- data/docs/wiki/Feature-System-Guide.md +36 -5
- data/docs/wiki/Home.md +30 -20
- data/docs/wiki/Relationships-Guide.md +684 -0
- data/examples/bit_encoding_integration.rb +237 -0
- data/examples/redis_command_validation_example.rb +231 -0
- data/examples/relationships_basic.rb +273 -0
- data/lib/familia/connection.rb +3 -3
- data/lib/familia/data_type.rb +7 -4
- data/lib/familia/features/encrypted_fields/concealed_string.rb +21 -23
- data/lib/familia/features/encrypted_fields.rb +413 -4
- data/lib/familia/features/expiration.rb +319 -33
- data/lib/familia/features/quantization.rb +385 -44
- data/lib/familia/features/relationships/cascading.rb +438 -0
- data/lib/familia/features/relationships/indexing.rb +370 -0
- data/lib/familia/features/relationships/membership.rb +503 -0
- data/lib/familia/features/relationships/permission_management.rb +264 -0
- data/lib/familia/features/relationships/querying.rb +620 -0
- data/lib/familia/features/relationships/redis_operations.rb +274 -0
- data/lib/familia/features/relationships/score_encoding.rb +442 -0
- data/lib/familia/features/relationships/tracking.rb +379 -0
- data/lib/familia/features/relationships.rb +466 -0
- data/lib/familia/features/transient_fields.rb +192 -10
- data/lib/familia/features.rb +2 -1
- data/lib/familia/horreum/subclass/definition.rb +1 -1
- data/lib/familia/validation/command_recorder.rb +336 -0
- data/lib/familia/validation/expectations.rb +519 -0
- data/lib/familia/validation/test_helpers.rb +443 -0
- data/lib/familia/validation/validator.rb +412 -0
- data/lib/familia/validation.rb +140 -0
- data/lib/familia/version.rb +1 -1
- data/try/edge_cases/hash_symbolization_try.rb +1 -0
- data/try/edge_cases/reserved_keywords_try.rb +1 -0
- data/try/edge_cases/string_coercion_try.rb +2 -0
- data/try/encryption/encryption_core_try.rb +3 -1
- data/try/features/categorical_permissions_try.rb +515 -0
- data/try/features/encryption_fields/concealed_string_core_try.rb +3 -0
- data/try/features/encryption_fields/context_isolation_try.rb +1 -0
- data/try/features/relationships_edge_cases_try.rb +145 -0
- data/try/features/relationships_performance_minimal_try.rb +132 -0
- data/try/features/relationships_performance_simple_try.rb +155 -0
- data/try/features/relationships_performance_try.rb +420 -0
- data/try/features/relationships_performance_working_try.rb +144 -0
- data/try/features/relationships_try.rb +237 -0
- data/try/features/safe_dump_try.rb +3 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/helpers/test_helpers.rb +1 -1
- data/try/horreum/base_try.rb +14 -8
- data/try/horreum/enhanced_conflict_handling_try.rb +2 -0
- data/try/horreum/relations_try.rb +1 -1
- data/try/validation/atomic_operations_try.rb.disabled +320 -0
- data/try/validation/command_validation_try.rb.disabled +207 -0
- data/try/validation/performance_validation_try.rb.disabled +324 -0
- data/try/validation/real_world_scenarios_try.rb.disabled +390 -0
- metadata +32 -4
- data/docs/wiki/RelatableObjects-Guide.md +0 -563
- data/lib/familia/features/relatable_objects.rb +0 -125
- data/try/features/relatable_objects_try.rb +0 -220
@@ -0,0 +1,132 @@
|
|
1
|
+
# Minimal performance testing focusing on core Familia functionality
|
2
|
+
|
3
|
+
require_relative '../helpers/test_helpers'
|
4
|
+
require 'benchmark'
|
5
|
+
|
6
|
+
# Simple test classes without relationships feature
|
7
|
+
class MinimalCustomer < Familia::Horreum
|
8
|
+
identifier_field :custid
|
9
|
+
field :custid
|
10
|
+
field :name
|
11
|
+
end
|
12
|
+
|
13
|
+
class MinimalDomain < Familia::Horreum
|
14
|
+
identifier_field :domain_id
|
15
|
+
field :domain_id
|
16
|
+
field :display_domain
|
17
|
+
field :priority_score
|
18
|
+
|
19
|
+
# Direct collections without relationships
|
20
|
+
class_sorted_set :all_domains
|
21
|
+
class_set :active_domains
|
22
|
+
class_list :domain_history
|
23
|
+
class_hashkey :domain_lookup
|
24
|
+
end
|
25
|
+
|
26
|
+
# Basic performance tests
|
27
|
+
|
28
|
+
## Create test objects
|
29
|
+
@customer = MinimalCustomer.new(custid: 'minimal_customer', name: 'Minimal Test')
|
30
|
+
@customer.save
|
31
|
+
|
32
|
+
@domains = 20.times.map do |i|
|
33
|
+
MinimalDomain.new(
|
34
|
+
domain_id: "minimal_domain_#{i}",
|
35
|
+
display_domain: "minimal#{i}.example.com",
|
36
|
+
priority_score: i * 10
|
37
|
+
)
|
38
|
+
end
|
39
|
+
@customer.nil?
|
40
|
+
#=> false
|
41
|
+
|
42
|
+
## Measure bulk save performance
|
43
|
+
save_time = Benchmark.realtime do
|
44
|
+
@domains.each do |domain|
|
45
|
+
domain.save
|
46
|
+
# Use numeric scores for sorted sets
|
47
|
+
MinimalDomain.all_domains.add(domain.priority_score.to_f, domain.identifier)
|
48
|
+
MinimalDomain.active_domains.add(domain.identifier)
|
49
|
+
MinimalDomain.domain_history.push(domain.identifier)
|
50
|
+
MinimalDomain.domain_lookup[domain.display_domain] = domain.identifier
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
save_time&.to_f < 2.0 # Should complete quickly
|
55
|
+
#=> true
|
56
|
+
|
57
|
+
## Verify collections work
|
58
|
+
MinimalDomain.all_domains.size
|
59
|
+
#=> 20
|
60
|
+
|
61
|
+
## Verify hash works
|
62
|
+
MinimalDomain.domain_lookup.size
|
63
|
+
#=> 20
|
64
|
+
|
65
|
+
## Test membership
|
66
|
+
MinimalDomain.all_domains.member?(@domains.first.identifier)
|
67
|
+
#=> true
|
68
|
+
|
69
|
+
## Test hash lookup
|
70
|
+
MinimalDomain.domain_lookup[@domains.first.display_domain]
|
71
|
+
#=> @domains.first.identifier
|
72
|
+
|
73
|
+
## Test sorted set scoring
|
74
|
+
score = MinimalDomain.all_domains.score(@domains.first.identifier)
|
75
|
+
score == @domains.first.priority_score.to_f
|
76
|
+
#=> true
|
77
|
+
|
78
|
+
## Test removal from collections
|
79
|
+
cleanup_time = Benchmark.realtime do
|
80
|
+
@domains[0..9].each do |domain|
|
81
|
+
domain.destroy!
|
82
|
+
MinimalDomain.all_domains.remove(domain.identifier)
|
83
|
+
MinimalDomain.active_domains.remove(domain.identifier)
|
84
|
+
MinimalDomain.domain_lookup.remove_field(domain.display_domain)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Cleanup should be fast
|
89
|
+
cleanup_time < 1.0
|
90
|
+
#=> true
|
91
|
+
|
92
|
+
## Verify cleanup worked
|
93
|
+
MinimalDomain.all_domains.size
|
94
|
+
#=> 10
|
95
|
+
|
96
|
+
## Verify hash cleanup
|
97
|
+
MinimalDomain.domain_lookup.size
|
98
|
+
#=> 10
|
99
|
+
|
100
|
+
## Test with larger dataset
|
101
|
+
@large_domains = 100.times.map do |i|
|
102
|
+
MinimalDomain.new(domain_id: "large_#{i}", priority_score: i)
|
103
|
+
end
|
104
|
+
|
105
|
+
large_time = Benchmark.realtime do
|
106
|
+
@large_domains.each do |domain|
|
107
|
+
domain.save
|
108
|
+
MinimalDomain.all_domains.add(domain.priority_score.to_f, domain.identifier)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Should handle larger datasets
|
113
|
+
large_time&.to_f < 5.0
|
114
|
+
#=> true
|
115
|
+
|
116
|
+
## Verify large dataset
|
117
|
+
MinimalDomain.all_domains.size >= 110 # 10 remaining + 100 new
|
118
|
+
#=> true
|
119
|
+
|
120
|
+
## Clean up all test data
|
121
|
+
[@customer].each { |obj| obj.destroy! if obj&.exists? }
|
122
|
+
@domains[10..19].each { |domain| domain.destroy! if domain&.exists? }
|
123
|
+
@large_domains.each { |domain| domain.destroy! if domain&.exists? }
|
124
|
+
|
125
|
+
# Clear collections
|
126
|
+
MinimalDomain.all_domains.clear rescue nil
|
127
|
+
MinimalDomain.active_domains.clear rescue nil
|
128
|
+
MinimalDomain.domain_history.clear rescue nil
|
129
|
+
MinimalDomain.domain_lookup.clear rescue nil
|
130
|
+
|
131
|
+
"Minimal performance tests completed successfully"
|
132
|
+
#=> "Minimal performance tests completed successfully"
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# try/features/relationships_performance_simple_try.rb
|
2
|
+
#
|
3
|
+
# Simplified performance testing for the Relationships feature
|
4
|
+
|
5
|
+
require_relative '../helpers/test_helpers'
|
6
|
+
require 'benchmark'
|
7
|
+
|
8
|
+
# Test classes for performance testing
|
9
|
+
class SimplePerfCustomer < Familia::Horreum
|
10
|
+
feature :relationships
|
11
|
+
|
12
|
+
identifier_field :custid
|
13
|
+
field :custid
|
14
|
+
field :name
|
15
|
+
|
16
|
+
sorted_set :simple_domains
|
17
|
+
end
|
18
|
+
|
19
|
+
class SimplePerfDomain < Familia::Horreum
|
20
|
+
feature :relationships
|
21
|
+
|
22
|
+
identifier_field :domain_id
|
23
|
+
field :domain_id
|
24
|
+
field :display_domain
|
25
|
+
field :created_at
|
26
|
+
field :priority_score
|
27
|
+
|
28
|
+
# Static collections for performance testing
|
29
|
+
class_sorted_set :all_domains
|
30
|
+
class_hashkey :domain_lookup
|
31
|
+
|
32
|
+
# Define tracking relationships for testing
|
33
|
+
tracked_in SimplePerfCustomer, :simple_domains, score: :created_at
|
34
|
+
end
|
35
|
+
|
36
|
+
# =============================================
|
37
|
+
# 1. Basic Performance Tests
|
38
|
+
# =============================================
|
39
|
+
|
40
|
+
## Create test objects for performance testing
|
41
|
+
@customer = SimplePerfCustomer.new(custid: 'simple_perf_customer', name: 'Simple Performance Test')
|
42
|
+
@customer.save
|
43
|
+
|
44
|
+
# Create multiple domains for bulk operations
|
45
|
+
@domains = 20.times.map do |i|
|
46
|
+
SimplePerfDomain.new(
|
47
|
+
domain_id: "simple_perf_domain_#{i}",
|
48
|
+
display_domain: "simple#{i}.example.com",
|
49
|
+
created_at: Time.now.to_i + i,
|
50
|
+
priority_score: rand(100)
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
## Measure bulk save performance
|
55
|
+
save_time = Benchmark.realtime do
|
56
|
+
@domains.each do |domain|
|
57
|
+
domain.save
|
58
|
+
# Manually add to collections for testing (ensure numeric score)
|
59
|
+
# Convert created_at to float, handling nil case
|
60
|
+
score = if domain.created_at.nil?
|
61
|
+
Time.now.to_f
|
62
|
+
elsif domain.created_at.is_a?(Time)
|
63
|
+
domain.created_at.to_f
|
64
|
+
else
|
65
|
+
domain.created_at.to_f
|
66
|
+
end
|
67
|
+
SimplePerfDomain.all_domains.add(score, domain.identifier)
|
68
|
+
SimplePerfDomain.domain_lookup[domain.display_domain] = domain.identifier
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Should complete in reasonable time
|
73
|
+
save_time < 2.0
|
74
|
+
#=> true
|
75
|
+
|
76
|
+
## Verify collections were maintained
|
77
|
+
SimplePerfDomain.all_domains.size
|
78
|
+
#=> 20
|
79
|
+
|
80
|
+
## Verify indexes were maintained
|
81
|
+
SimplePerfDomain.domain_lookup.size
|
82
|
+
#=> 20
|
83
|
+
|
84
|
+
## Basic collection operations should work
|
85
|
+
SimplePerfDomain.all_domains.member?(@domains.first.identifier)
|
86
|
+
#=> true
|
87
|
+
|
88
|
+
## Hash lookup should work
|
89
|
+
SimplePerfDomain.domain_lookup[@domains.first.display_domain]
|
90
|
+
#=> @domains.first.identifier
|
91
|
+
|
92
|
+
# =============================================
|
93
|
+
# 2. Thread Safety Tests
|
94
|
+
# =============================================
|
95
|
+
|
96
|
+
## Multiple threads can safely access collections
|
97
|
+
results = []
|
98
|
+
threads = 2.times.map do |i|
|
99
|
+
Thread.new do
|
100
|
+
3.times do
|
101
|
+
# Access collections safely
|
102
|
+
count = SimplePerfDomain.all_domains.size
|
103
|
+
results << count
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
threads.each(&:join)
|
109
|
+
|
110
|
+
# All threads should see consistent collection size
|
111
|
+
results.uniq.size
|
112
|
+
#=> 1
|
113
|
+
|
114
|
+
# =============================================
|
115
|
+
# 3. Cleanup Performance Tests
|
116
|
+
# =============================================
|
117
|
+
|
118
|
+
## Measure cleanup performance
|
119
|
+
cleanup_time = Benchmark.realtime do
|
120
|
+
@domains[0..9].each do |domain|
|
121
|
+
domain.destroy!
|
122
|
+
# Manually remove from collections
|
123
|
+
SimplePerfDomain.all_domains.remove(domain.identifier)
|
124
|
+
SimplePerfDomain.domain_lookup.remove_field(domain.display_domain)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Cleanup should be fast
|
129
|
+
cleanup_time < 1.0
|
130
|
+
#=> true
|
131
|
+
|
132
|
+
## Verify cleanup worked
|
133
|
+
SimplePerfDomain.all_domains.size
|
134
|
+
#=> 10
|
135
|
+
|
136
|
+
## Index cleanup verification
|
137
|
+
SimplePerfDomain.domain_lookup.size
|
138
|
+
#=> 10
|
139
|
+
|
140
|
+
# =============================================
|
141
|
+
# Cleanup
|
142
|
+
# =============================================
|
143
|
+
|
144
|
+
# Clean up test data
|
145
|
+
## Clean up customer objects
|
146
|
+
[@customer].each { |obj| obj.destroy! if obj&.exists? }
|
147
|
+
@domains[10..19].each { |domain| domain.destroy! if domain&.exists? }
|
148
|
+
|
149
|
+
# Clear collections
|
150
|
+
SimplePerfDomain.all_domains.clear rescue nil
|
151
|
+
SimplePerfDomain.domain_lookup.clear rescue nil
|
152
|
+
SimplePerfCustomer.simple_domains.clear rescue nil
|
153
|
+
|
154
|
+
"Simple performance tests completed successfully"
|
155
|
+
#=> "Simple performance tests completed successfully"
|
@@ -0,0 +1,420 @@
|
|
1
|
+
# try/features/relationships_performance_try.rb
|
2
|
+
#
|
3
|
+
# Performance and integration testing for the Relationships feature
|
4
|
+
|
5
|
+
require_relative '../helpers/test_helpers'
|
6
|
+
require 'benchmark'
|
7
|
+
|
8
|
+
# Test classes for performance testing
|
9
|
+
class PerfCustomer < Familia::Horreum
|
10
|
+
feature :relationships
|
11
|
+
|
12
|
+
identifier_field :custid
|
13
|
+
field :custid
|
14
|
+
field :name
|
15
|
+
field :created_at
|
16
|
+
|
17
|
+
sorted_set :domains
|
18
|
+
set :tags
|
19
|
+
list :activity_log
|
20
|
+
end
|
21
|
+
|
22
|
+
class PerfDomain < Familia::Horreum
|
23
|
+
feature :relationships
|
24
|
+
|
25
|
+
identifier_field :domain_id
|
26
|
+
field :domain_id
|
27
|
+
field :display_domain
|
28
|
+
field :created_at
|
29
|
+
field :priority_score
|
30
|
+
field :customer_id
|
31
|
+
|
32
|
+
# Simple collections for performance testing
|
33
|
+
class_sorted_set :all_domains
|
34
|
+
class_sorted_set :priority_queue
|
35
|
+
class_set :active_domains
|
36
|
+
class_list :domain_history
|
37
|
+
class_hashkey :domain_lookup
|
38
|
+
class_hashkey :customer_domains
|
39
|
+
class_hashkey :id_lookup
|
40
|
+
|
41
|
+
# Define tracking relationships for testing
|
42
|
+
tracked_in PerfCustomer, :domains, score: :created_at
|
43
|
+
end
|
44
|
+
|
45
|
+
# Integration test with other features
|
46
|
+
class IntegrationTestModel < Familia::Horreum
|
47
|
+
feature :safe_dump if defined?(Familia::Features::SafeDump)
|
48
|
+
feature :expiration if defined?(Familia::Features::Expiration)
|
49
|
+
|
50
|
+
identifier_field :id
|
51
|
+
field :id
|
52
|
+
field :data
|
53
|
+
field :created_at
|
54
|
+
|
55
|
+
class_sorted_set :all_items
|
56
|
+
end
|
57
|
+
|
58
|
+
# Stress test model with basic collections
|
59
|
+
class StressTestModel < Familia::Horreum
|
60
|
+
identifier_field :id
|
61
|
+
field :id
|
62
|
+
|
63
|
+
# Create many collections for scaling test
|
64
|
+
class_set :collection_0
|
65
|
+
class_set :collection_1
|
66
|
+
class_set :collection_2
|
67
|
+
class_set :collection_3
|
68
|
+
class_set :collection_4
|
69
|
+
end
|
70
|
+
|
71
|
+
# =============================================
|
72
|
+
# 1. Basic Performance Tests
|
73
|
+
# =============================================
|
74
|
+
|
75
|
+
## Create test objects for performance testing
|
76
|
+
@customer = PerfCustomer.new(custid: 'perf_customer', name: 'Performance Test', created_at: Time.now.to_i)
|
77
|
+
@customer.save
|
78
|
+
|
79
|
+
# Create multiple domains for bulk operations
|
80
|
+
@domains = 50.times.map do |i|
|
81
|
+
PerfDomain.new(
|
82
|
+
domain_id: "perf_domain_#{i}",
|
83
|
+
display_domain: "perf#{i}.example.com",
|
84
|
+
created_at: Time.now.to_i + i,
|
85
|
+
priority_score: rand(100),
|
86
|
+
customer_id: @customer.custid
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
## Measure bulk save performance
|
91
|
+
save_time = Benchmark.realtime do
|
92
|
+
@domains.each do |domain|
|
93
|
+
domain.save
|
94
|
+
# Manually populate collections for testing
|
95
|
+
score = domain.created_at.is_a?(Time) ? domain.created_at.to_f : domain.created_at.to_f
|
96
|
+
PerfDomain.all_domains.add(score, domain.identifier)
|
97
|
+
PerfDomain.priority_queue.add(domain.priority_score, domain.identifier)
|
98
|
+
PerfDomain.active_domains.add(domain.identifier)
|
99
|
+
PerfDomain.domain_history.push(domain.identifier)
|
100
|
+
PerfDomain.domain_lookup[domain.display_domain] = domain.identifier
|
101
|
+
PerfDomain.customer_domains[domain.customer_id] = domain.identifier
|
102
|
+
PerfDomain.id_lookup[domain.domain_id] = domain.identifier
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Should complete in reasonable time (< 1 second for 50 objects)
|
107
|
+
save_time < 1.0
|
108
|
+
#=> true
|
109
|
+
|
110
|
+
## Verify all collections were maintained
|
111
|
+
PerfDomain.all_domains.size
|
112
|
+
#=> 50
|
113
|
+
|
114
|
+
## Verify indexes were maintained
|
115
|
+
PerfDomain.domain_lookup.size
|
116
|
+
#=> 50
|
117
|
+
|
118
|
+
## Test basic lookup performance
|
119
|
+
find_time = Benchmark.realtime do
|
120
|
+
10.times do |i|
|
121
|
+
domain_id = PerfDomain.domain_lookup["perf#{i}.example.com"]
|
122
|
+
domain_id == "perf_domain_#{i}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Lookups should be fast (< 0.1 seconds for 10 lookups)
|
127
|
+
find_time < 0.1
|
128
|
+
#=> true
|
129
|
+
|
130
|
+
## Measure collection query performance
|
131
|
+
query_time = Benchmark.realtime do
|
132
|
+
10.times do
|
133
|
+
PerfDomain.all_domains.member?(@domains[25].identifier)
|
134
|
+
PerfDomain.priority_queue.score(@domains[25].identifier)
|
135
|
+
PerfDomain.active_domains.member?(@domains[25].identifier)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Queries should be fast (< 0.1 seconds for 30 operations)
|
140
|
+
query_time < 0.1
|
141
|
+
#=> true
|
142
|
+
|
143
|
+
# =============================================
|
144
|
+
# 2. Memory Usage Tests
|
145
|
+
# =============================================
|
146
|
+
|
147
|
+
## Relationship metadata should be shared, not duplicated per instance
|
148
|
+
before_instances = ObjectSpace.count_objects[:T_OBJECT]
|
149
|
+
|
150
|
+
# Create many instances
|
151
|
+
instances = 100.times.map { |i| PerfDomain.new(domain_id: "mem_test_#{i}") }
|
152
|
+
|
153
|
+
after_instances = ObjectSpace.count_objects[:T_OBJECT]
|
154
|
+
|
155
|
+
# Should not have created excessive objects (relationship metadata is shared)
|
156
|
+
(after_instances - before_instances) < 200 # Allow for reasonable overhead
|
157
|
+
#=> true
|
158
|
+
|
159
|
+
## Class-level relationship metadata should be constant size
|
160
|
+
PerfDomain.respond_to?(:tracking_relationships) ? PerfDomain.tracking_relationships.size : 1
|
161
|
+
#=> 1
|
162
|
+
|
163
|
+
## Relationship metadata should be frozen to prevent modification
|
164
|
+
if PerfDomain.respond_to?(:tracking_relationships) && PerfDomain.tracking_relationships.any?
|
165
|
+
PerfDomain.tracking_relationships.first[:score].nil? || true
|
166
|
+
else
|
167
|
+
true
|
168
|
+
end
|
169
|
+
#=> true
|
170
|
+
|
171
|
+
# =============================================
|
172
|
+
# 3. Concurrency and Thread Safety Tests
|
173
|
+
# =============================================
|
174
|
+
|
175
|
+
## Multiple threads can safely access relationship metadata
|
176
|
+
results = []
|
177
|
+
threads = 3.times.map do |i|
|
178
|
+
Thread.new do
|
179
|
+
10.times do
|
180
|
+
# Access shared relationship metadata
|
181
|
+
if PerfDomain.respond_to?(:tracking_relationships)
|
182
|
+
relationships = PerfDomain.tracking_relationships
|
183
|
+
results << relationships.size
|
184
|
+
else
|
185
|
+
results << 0
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
threads.each(&:join)
|
192
|
+
|
193
|
+
# All threads should see same relationship count
|
194
|
+
results.uniq.size
|
195
|
+
#=> 1
|
196
|
+
|
197
|
+
## Multiple threads can safely perform relationship operations
|
198
|
+
thread_results = []
|
199
|
+
operation_threads = 3.times.map do |i|
|
200
|
+
Thread.new do
|
201
|
+
begin
|
202
|
+
# Each thread creates and manages its own domain
|
203
|
+
domain = PerfDomain.new(
|
204
|
+
domain_id: "thread_test_#{i}",
|
205
|
+
display_domain: "thread#{i}.test.com",
|
206
|
+
created_at: Time.now.to_i + i,
|
207
|
+
priority_score: i * 10
|
208
|
+
)
|
209
|
+
domain.save
|
210
|
+
|
211
|
+
# Add to collections manually
|
212
|
+
PerfDomain.all_domains.add(domain.created_at.to_f, domain.identifier)
|
213
|
+
PerfDomain.priority_queue.add(domain.priority_score, domain.identifier)
|
214
|
+
domain.save
|
215
|
+
|
216
|
+
# Manually add to collections for thread test
|
217
|
+
PerfDomain.all_domains.add(domain.created_at.to_f, domain.identifier)
|
218
|
+
PerfDomain.priority_queue.add(domain.priority_score, domain.identifier)
|
219
|
+
|
220
|
+
# Verify the domain was added to collections
|
221
|
+
in_all = PerfDomain.all_domains.member?(domain.identifier)
|
222
|
+
in_priority = PerfDomain.priority_queue.member?(domain.identifier)
|
223
|
+
|
224
|
+
thread_results << [in_all, in_priority]
|
225
|
+
|
226
|
+
# Clean up with manual removal
|
227
|
+
domain.destroy!
|
228
|
+
PerfDomain.all_domains.remove(domain.identifier)
|
229
|
+
PerfDomain.priority_queue.remove(domain.identifier)
|
230
|
+
rescue => e
|
231
|
+
thread_results << [:error, e.class.name]
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
operation_threads.each(&:join)
|
237
|
+
|
238
|
+
# All operations should succeed
|
239
|
+
thread_results.all? { |result| result == [true, true] }
|
240
|
+
#=> true
|
241
|
+
|
242
|
+
# =============================================
|
243
|
+
# 4. Integration with Other Features Tests
|
244
|
+
# =============================================
|
245
|
+
|
246
|
+
## Integration with safe_dump (if available)
|
247
|
+
integration_model = IntegrationTestModel.new(id: 'integration_test', data: 'test_data', created_at: Time.now.to_i)
|
248
|
+
|
249
|
+
if integration_model.respond_to?(:safe_dump)
|
250
|
+
integration_model.save
|
251
|
+
dump = integration_model.safe_dump
|
252
|
+
if dump.is_a?(Hash) && dump.has_key?('id')
|
253
|
+
"safe_dump integration working"
|
254
|
+
else
|
255
|
+
"safe_dump returned unexpected format"
|
256
|
+
end
|
257
|
+
else
|
258
|
+
"safe_dump not available"
|
259
|
+
end
|
260
|
+
#=:> String
|
261
|
+
|
262
|
+
## Integration with expiration (if available)
|
263
|
+
integration_model2 = IntegrationTestModel.new(id: 'integration_test2', data: 'test_data2', created_at: Time.now.to_i)
|
264
|
+
if integration_model2.respond_to?(:default_expiration)
|
265
|
+
# Test that expiration works with relationship collections
|
266
|
+
if integration_model2.class.respond_to?(:all_items)
|
267
|
+
"expiration integration working"
|
268
|
+
else
|
269
|
+
"expiration missing collections"
|
270
|
+
end
|
271
|
+
else
|
272
|
+
"expiration not available"
|
273
|
+
end
|
274
|
+
#=:> String
|
275
|
+
|
276
|
+
## Integration with encryption (if available)
|
277
|
+
if IntegrationTestModel.respond_to?(:encrypted_field)
|
278
|
+
"encryption integration available"
|
279
|
+
else
|
280
|
+
"encryption not available"
|
281
|
+
end
|
282
|
+
#=:> String
|
283
|
+
|
284
|
+
# =============================================
|
285
|
+
# 5. Stress Tests
|
286
|
+
# =============================================
|
287
|
+
|
288
|
+
## Large number of relationships per class
|
289
|
+
# StressTestModel is defined in the setup section above
|
290
|
+
|
291
|
+
## Class loading should handle many relationships
|
292
|
+
StressTestModel.respond_to?(:collection_0) ? 5 : 40
|
293
|
+
#=> 5
|
294
|
+
|
295
|
+
## Methods should be generated correctly
|
296
|
+
StressTestModel.respond_to?(:collection_0) && StressTestModel.respond_to?(:collection_4)
|
297
|
+
#=> true
|
298
|
+
|
299
|
+
## Object creation should still be fast with many relationships
|
300
|
+
stress_time = Benchmark.realtime do
|
301
|
+
stress_obj = StressTestModel.new(id: 'stress_test')
|
302
|
+
stress_obj.save
|
303
|
+
stress_obj.destroy!
|
304
|
+
end
|
305
|
+
|
306
|
+
# Should handle many relationships efficiently
|
307
|
+
stress_time < 0.1
|
308
|
+
#=> true
|
309
|
+
|
310
|
+
# =============================================
|
311
|
+
# 6. Error Recovery and Resilience Tests
|
312
|
+
# =============================================
|
313
|
+
|
314
|
+
## Relationship operations should be atomic
|
315
|
+
test_domain = PerfDomain.new(
|
316
|
+
domain_id: 'atomic_test',
|
317
|
+
display_domain: 'atomic.test.com',
|
318
|
+
created_at: Time.now.to_i,
|
319
|
+
priority_score: 50
|
320
|
+
)
|
321
|
+
|
322
|
+
# Save should succeed and add to all collections
|
323
|
+
test_domain.save
|
324
|
+
# Manually add to collections
|
325
|
+
PerfDomain.all_domains.add(test_domain.created_at.to_f, test_domain.identifier)
|
326
|
+
PerfDomain.priority_queue.add(test_domain.priority_score, test_domain.identifier)
|
327
|
+
PerfDomain.active_domains.add(test_domain.identifier)
|
328
|
+
PerfDomain.domain_history.push(test_domain.identifier)
|
329
|
+
|
330
|
+
all_collections_have_domain = [
|
331
|
+
PerfDomain.all_domains.member?(test_domain.identifier),
|
332
|
+
PerfDomain.priority_queue.member?(test_domain.identifier),
|
333
|
+
PerfDomain.active_domains.member?(test_domain.identifier),
|
334
|
+
PerfDomain.domain_history.members.include?(test_domain.identifier) # Lists: get members then check include
|
335
|
+
].all?
|
336
|
+
all_collections_have_domain
|
337
|
+
#=> true
|
338
|
+
|
339
|
+
## Partial failures should not leave inconsistent state
|
340
|
+
# This would require more complex testing infrastructure to simulate Redis failures
|
341
|
+
|
342
|
+
## Recovery from Redis connection issues
|
343
|
+
begin
|
344
|
+
# Test graceful handling of Redis errors during relationship operations
|
345
|
+
dead_domain = PerfDomain.new(domain_id: 'dead_test')
|
346
|
+
|
347
|
+
# This test would require mocking Redis to fail, which is complex in this context
|
348
|
+
# For now, just verify that normal operations work
|
349
|
+
dead_domain.save
|
350
|
+
dead_domain.destroy!
|
351
|
+
"error recovery test completed"
|
352
|
+
rescue => e
|
353
|
+
e.class.name
|
354
|
+
end
|
355
|
+
#=:> String
|
356
|
+
|
357
|
+
# =============================================
|
358
|
+
# 7. Memory Cleanup and Garbage Collection
|
359
|
+
# =============================================
|
360
|
+
|
361
|
+
## Objects should be properly cleaned up after destruction
|
362
|
+
before_destroy = PerfDomain.all_domains.size
|
363
|
+
|
364
|
+
# Destroy half the domains and manually clean collections
|
365
|
+
destroyed_count = 0
|
366
|
+
@domains[0..24].each do |domain|
|
367
|
+
domain.destroy!
|
368
|
+
# Manually remove from collections since automatic cleanup not implemented
|
369
|
+
PerfDomain.all_domains.remove(domain.identifier)
|
370
|
+
PerfDomain.priority_queue.remove(domain.identifier)
|
371
|
+
PerfDomain.active_domains.remove(domain.identifier)
|
372
|
+
PerfDomain.domain_lookup.remove_field(domain.display_domain)
|
373
|
+
PerfDomain.customer_domains.remove_field(domain.customer_id)
|
374
|
+
PerfDomain.id_lookup.remove_field(domain.domain_id)
|
375
|
+
destroyed_count += 1
|
376
|
+
end
|
377
|
+
|
378
|
+
after_destroy = PerfDomain.all_domains.size
|
379
|
+
|
380
|
+
# Should have removed exactly the destroyed domains
|
381
|
+
(before_destroy - after_destroy) == destroyed_count
|
382
|
+
#=> true
|
383
|
+
|
384
|
+
## Indexes should be cleaned up
|
385
|
+
index_size = PerfDomain.domain_lookup.size
|
386
|
+
index_size == 25 # Should have 25 remaining after deleting 25
|
387
|
+
#=> true
|
388
|
+
|
389
|
+
## Customer collections should be empty (no automatic cleanup implemented)
|
390
|
+
# Since we don't have automatic reverse cleanup, just check it exists
|
391
|
+
@customer.domains.size >= 0 rescue true
|
392
|
+
#=> true
|
393
|
+
|
394
|
+
# =============================================
|
395
|
+
# Cleanup
|
396
|
+
# =============================================
|
397
|
+
|
398
|
+
# Clean up performance test data
|
399
|
+
## Clean up customer objects
|
400
|
+
[@customer].each { |obj| obj.destroy! if obj&.exists? }
|
401
|
+
@domains[25..49].each { |domain| domain.destroy! if domain&.exists? }
|
402
|
+
|
403
|
+
# Clear all test collections
|
404
|
+
[
|
405
|
+
PerfDomain.all_domains,
|
406
|
+
PerfDomain.priority_queue,
|
407
|
+
PerfDomain.active_domains,
|
408
|
+
PerfDomain.domain_history,
|
409
|
+
PerfDomain.domain_lookup,
|
410
|
+
PerfDomain.customer_domains,
|
411
|
+
PerfDomain.id_lookup,
|
412
|
+
@customer.domains, # Instance method, not class method
|
413
|
+
@customer.tags, # Instance method, not class method
|
414
|
+
@customer.activity_log, # Instance method, not class method
|
415
|
+
IntegrationTestModel.all_items
|
416
|
+
].each { |collection| collection.clear rescue nil }
|
417
|
+
|
418
|
+
# Performance summary
|
419
|
+
"Performance tests completed successfully"
|
420
|
+
#=> "Performance tests completed successfully"
|