haveapi 0.30.2 → 0.31.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/benchmarks/Gemfile +30 -0
- data/benchmarks/Gemfile.lock +296 -0
- data/benchmarks/endpoints.rb +70 -14
- data/benchmarks/performance_compare.rb +112 -13
- data/benchmarks/shell.nix +21 -0
- data/doc/client-examples.md +81 -35
- data/doc/performance.md +99 -61
- data/haveapi.gemspec +1 -1
- data/lib/haveapi/client_examples/ts_generated_client.rb +36 -0
- data/lib/haveapi/documentation_clients.rb +49 -6
- data/lib/haveapi/version.rb +1 -1
- data/lib/haveapi/views/version_page.erb +2 -2
- data/spec/client_examples/profile_mount_spec.rb +45 -0
- data/spec/client_examples/ts_generated_client_spec.rb +53 -0
- data/spec/client_examples/typescript_profiles_spec.rb +113 -0
- data/spec/documentation/client_examples_spec.rb +2 -2
- data/spec/performance/comparator_spec.rb +136 -6
- data/spec/performance/event_baseline_spec.rb +156 -28
- data/spec/performance/release_lineage_spec.rb +53 -14
- data/spec/performance/runner_spec.rb +134 -0
- metadata +9 -3
|
@@ -3,6 +3,7 @@ require 'open3'
|
|
|
3
3
|
require 'tmpdir'
|
|
4
4
|
require 'fileutils'
|
|
5
5
|
require_relative '../../benchmarks/endpoints'
|
|
6
|
+
require_relative '../../../../utils/performance_ci'
|
|
6
7
|
require_relative 'support/samples'
|
|
7
8
|
require_relative '../../test_support/performance_endpoint_fixture'
|
|
8
9
|
require_relative '../../test_support/documentation_performance_fixture'
|
|
@@ -109,6 +110,70 @@ describe HaveAPI::Performance::Worker do
|
|
|
109
110
|
end
|
|
110
111
|
end
|
|
111
112
|
|
|
113
|
+
it 'retains the single benchmark bundle and raw unused contributor flakes without library locks' do
|
|
114
|
+
root = File.expand_path('../../../..', __dir__)
|
|
115
|
+
inputs = JSON.parse(JSON.generate(described_class.inputs(root, 'quick', benchmark: true)))
|
|
116
|
+
tool = inputs.fetch('benchmark')
|
|
117
|
+
expect(tool.keys).to match_array(HaveAPI::Performance::BENCHMARK_INPUTS)
|
|
118
|
+
expect(inputs.fetch('locks')).to eq('servers/ruby/benchmarks/Gemfile.lock' => tool.fetch('servers/ruby/benchmarks/Gemfile.lock').fetch('sha256'))
|
|
119
|
+
expect(inputs.fetch('manifests')).to have_key('.ruby-version')
|
|
120
|
+
expect(inputs.fetch('manifests')).not_to have_key('flake.lock')
|
|
121
|
+
expect(inputs.fetch('contributor_flakes').keys).to match_array(%w[flake.nix flake.lock])
|
|
122
|
+
tool.each do |path, input|
|
|
123
|
+
expect(input.fetch('content')).to eq(File.read(File.join(root, path)))
|
|
124
|
+
expect(input.fetch('sha256')).to eq(Digest::SHA256.hexdigest(input.fetch('content')))
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'rejects changed Ruby and unavailable calibrated dependencies before starting workers' do
|
|
129
|
+
samples, json = PerformanceSamples.fixture
|
|
130
|
+
Dir.mktmpdir('haveapi-runtime-guard-') do |directory|
|
|
131
|
+
budget = File.join(directory, 'budgets.json')
|
|
132
|
+
cli = HaveAPI::Performance::CLI.new(['run', '--baseline-root', directory, '--baseline-sha', samples['baseline_sha'],
|
|
133
|
+
'--candidate-root', directory, '--candidate-sha', samples['candidate_sha'],
|
|
134
|
+
'--budgets', budget, '--output', File.join(directory, 'result')])
|
|
135
|
+
cli.send(:parse)
|
|
136
|
+
File.write(budget, json)
|
|
137
|
+
allow(cli).to receive(:child)
|
|
138
|
+
expect { cli.send(:run_samples, 'f' * 64) }.to raise_error(HaveAPI::Performance::ContractError, 'Calibration Ruby mismatch')
|
|
139
|
+
budgets = JSON.parse(json)
|
|
140
|
+
budgets['environment']['weekly']['ruby'] = JSON.parse(JSON.generate(described_class.new({}).environment.fetch(:ruby)))
|
|
141
|
+
budgets['environment']['weekly']['gems'] = { 'haveapi-missing-calibrated-gem' => { 'version' => '99.0', 'platform' => 'ruby' } }
|
|
142
|
+
File.write(budget, JSON.generate(budgets))
|
|
143
|
+
expect { cli.send(:run_samples, 'f' * 64) }.to raise_error(HaveAPI::Performance::ContractError, /Missing calibrated dependency/)
|
|
144
|
+
expect(cli).not_to have_received(:child)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'limits the nonancestor runner exception to an exact quick push event pair' do
|
|
149
|
+
samples, = PerformanceSamples.fixture
|
|
150
|
+
Dir.mktmpdir('haveapi-push-guard-') do |directory|
|
|
151
|
+
event = File.join(directory, 'selection.json')
|
|
152
|
+
selection = samples.slice('baseline_sha', 'candidate_sha').merge('event_name' => 'push', 'profile' => 'quick',
|
|
153
|
+
'push_pair' => { 'event_name' => 'push', 'before' => samples['baseline_sha'], 'after' => samples['candidate_sha'] })
|
|
154
|
+
%w[valid changed missing weekly].each do |control|
|
|
155
|
+
value = Marshal.load(Marshal.dump(selection))
|
|
156
|
+
value['push_pair']['before'] = 'c' * 40 if control == 'changed'
|
|
157
|
+
value.delete('push_pair') if control == 'missing'
|
|
158
|
+
File.write(event, JSON.generate(value))
|
|
159
|
+
cli = HaveAPI::Performance::CLI.new(['run', '--baseline-root', directory, '--baseline-sha', samples['baseline_sha'],
|
|
160
|
+
'--candidate-root', directory, '--candidate-sha', samples['candidate_sha'],
|
|
161
|
+
'--profile', control == 'weekly' ? 'weekly' : 'quick', '--push-event', event,
|
|
162
|
+
'--budgets', '/unused', '--output', '/unused'])
|
|
163
|
+
cli.send(:parse)
|
|
164
|
+
allow(cli).to receive(:verify_runtime)
|
|
165
|
+
allow(HaveAPI::Performance).to receive(:gemspec)
|
|
166
|
+
allow(cli).to receive(:verify_source) { |role| samples['sources'].fetch(role) }
|
|
167
|
+
allow(cli).to receive(:ancestor?).and_return(false)
|
|
168
|
+
allow(HaveAPI::Performance::Release).to receive(:lineage).and_return(nil)
|
|
169
|
+
allow(cli).to receive(:child).and_raise(HaveAPI::Performance::ContractError, 'pilot boundary')
|
|
170
|
+
expect { cli.send(:run_samples, 'f' * 64) }.to raise_error(HaveAPI::Performance::ContractError,
|
|
171
|
+
control == 'valid' ? 'pilot boundary' : /push|Push|Weekly/)
|
|
172
|
+
expect(cli).not_to have_received(:child) unless control == 'valid'
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
112
177
|
it 'validates every response after the timed loop' do
|
|
113
178
|
scenario = Class.new do
|
|
114
179
|
attr_reader :calls, :validations
|
|
@@ -252,6 +317,8 @@ describe HaveAPI::Performance::Worker do
|
|
|
252
317
|
'--candidate-root', '/controlled/candidate', '--candidate-sha', samples['candidate_sha'],
|
|
253
318
|
'--budgets', '/unused', '--output', '/unused'])
|
|
254
319
|
cli.send(:parse)
|
|
320
|
+
allow(cli).to receive(:verify_runtime)
|
|
321
|
+
allow(HaveAPI::Performance).to receive(:gemspec)
|
|
255
322
|
allow(cli).to receive(:verify_source) { |role| samples['sources'].fetch(role) }
|
|
256
323
|
allow(cli).to receive(:ancestor?).and_return(true)
|
|
257
324
|
allow(HaveAPI::Performance::Release).to receive(:lineage).and_return(nil)
|
|
@@ -354,6 +421,8 @@ describe HaveAPI::Performance::Worker do
|
|
|
354
421
|
'--candidate-sha', samples['candidate_sha'], '--budgets', policy, '--output', output])
|
|
355
422
|
# Control source verification and worker replies, retaining the real run
|
|
356
423
|
# assembly, child protocol validation, direct comparator and report path.
|
|
424
|
+
allow(cli).to receive(:verify_runtime)
|
|
425
|
+
allow(HaveAPI::Performance).to receive(:gemspec)
|
|
357
426
|
allow(cli).to receive_messages(git: '', ancestor?: true)
|
|
358
427
|
allow(HaveAPI::Performance::Release).to receive(:lineage).and_return(nil)
|
|
359
428
|
allow(cli).to receive(:verify_source) { |role| samples['sources'].fetch(role).transform_keys(&:to_sym) }
|
|
@@ -387,6 +456,9 @@ describe HaveAPI::Performance::Worker do
|
|
|
387
456
|
expect(cli.execute).to eq(0)
|
|
388
457
|
retained = JSON.parse(File.read(File.join(output, 'samples.json')))
|
|
389
458
|
expect(retained['sources']).to eq(samples['sources'])
|
|
459
|
+
saved_report = JSON.parse(File.read(File.join(output, 'report.json')))
|
|
460
|
+
expect(saved_report.dig('comparison', 'samples_sha256')).to eq(Digest::SHA256.file(File.join(output, 'samples.json')).hexdigest)
|
|
461
|
+
expect(saved_report['harness_sha256']).to eq(retained['harness_sha256'])
|
|
390
462
|
report = HaveAPI::Performance::Comparator.new(retained, budgets, budget_sha256: Digest::SHA256.hexdigest(budget_json)).compare
|
|
391
463
|
expect(report['status']).to eq('pass')
|
|
392
464
|
expect(requests.map(&:first).count('calibration-adjusted')).to eq(expensive ? 0 : 1)
|
|
@@ -463,14 +535,76 @@ describe HaveAPI::Performance::Worker do
|
|
|
463
535
|
cli = HaveAPI::Performance::CLI.new(['run', '--baseline-root', directory, '--baseline-sha', '0' * 40,
|
|
464
536
|
'--candidate-root', directory, '--candidate-sha', '1' * 40,
|
|
465
537
|
'--budgets', budget, '--output', missing])
|
|
538
|
+
allow(cli).to receive(:verify_runtime)
|
|
466
539
|
expect(cli.execute).to eq(3)
|
|
467
540
|
report = JSON.parse(File.read(File.join(missing, 'report.json')))
|
|
468
541
|
expect(report['error']['message']).to include('Source SHA must be full immutable SHA')
|
|
469
542
|
expect(report.values_at('baseline_sha', 'candidate_sha')).to eq(['0' * 40, '1' * 40])
|
|
543
|
+
expect(report).not_to have_key('harness_sha256')
|
|
544
|
+
expect(report.fetch('comparison')).not_to have_key('samples_sha256')
|
|
470
545
|
equal = HaveAPI::Performance::CLI.new(['run', '--baseline-root', directory, '--baseline-sha', 'a' * 40,
|
|
471
546
|
'--candidate-root', directory, '--candidate-sha', 'a' * 40,
|
|
472
547
|
'--budgets', budget, '--output', File.join(directory, 'equal')])
|
|
473
548
|
expect(equal.execute).to eq(3)
|
|
474
549
|
end
|
|
475
550
|
end
|
|
551
|
+
|
|
552
|
+
it 'checks declared Ruby and runtime requirements against installed tools including unloaded rake' do
|
|
553
|
+
campaign = HaveAPI::PerformanceCI::Campaign.new(repository: instance_double(HaveAPI::PerformanceCI::Repository),
|
|
554
|
+
selection: {}, evidence: '/unused', sources: '/unused')
|
|
555
|
+
spec = { 'ruby' => '>= 3.2', 'dependencies' => [['rake', 'runtime', '>= 13.0'], ['haveapi-client', 'runtime', '~> 0.30.0']] }
|
|
556
|
+
status = instance_double(Process::Status, success?: true)
|
|
557
|
+
allow(Open3).to receive(:capture3).and_return([JSON.generate(spec), '', status])
|
|
558
|
+
expect(campaign.send(:gemspec, '/source', 'servers/ruby/haveapi.gemspec')).to eq(spec)
|
|
559
|
+
changed = spec.merge('ruby' => '>= 99')
|
|
560
|
+
allow(Open3).to receive(:capture3).and_return([JSON.generate(changed), '', status])
|
|
561
|
+
expect { campaign.send(:gemspec, '/source', 'servers/ruby/haveapi.gemspec') }.to raise_error(HaveAPI::PerformanceCI::Error, /Benchmark Ruby/)
|
|
562
|
+
changed = spec.merge('dependencies' => [['rake', 'runtime', '>= 99']])
|
|
563
|
+
allow(Open3).to receive(:capture3).and_return([JSON.generate(changed), '', status])
|
|
564
|
+
expect { campaign.send(:gemspec, '/source', 'servers/ruby/haveapi.gemspec') }.to raise_error(HaveAPI::PerformanceCI::Error, /Benchmark dependencies/)
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
it 'rejects unsupported Ruby or gem requirements in either direct source before the first child' do
|
|
568
|
+
samples, = PerformanceSamples.fixture
|
|
569
|
+
Dir.mktmpdir('haveapi-source-requirements-') do |directory|
|
|
570
|
+
roots = %w[baseline candidate].to_h { |role| [role, File.join(directory, role)] }
|
|
571
|
+
paths = HaveAPI::Performance::Release::VERSIONS.map { |path, name| "#{path.split('/lib/').first}/#{name}.gemspec" }
|
|
572
|
+
valid = <<~RUBY
|
|
573
|
+
Gem::Specification.new do |spec|
|
|
574
|
+
spec.name = 'haveapi-fixture'
|
|
575
|
+
spec.version = '1.0.0'
|
|
576
|
+
spec.required_ruby_version = '>= 3.2'
|
|
577
|
+
spec.add_dependency 'rake', '>= 13.0'
|
|
578
|
+
end
|
|
579
|
+
RUBY
|
|
580
|
+
roots.each_value do |root|
|
|
581
|
+
paths.each do |path|
|
|
582
|
+
FileUtils.mkdir_p(File.dirname(File.join(root, path)))
|
|
583
|
+
File.write(File.join(root, path), valid)
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
%w[baseline candidate].product(%w[valid ruby dependency]).each do |role, fault|
|
|
587
|
+
path = File.join(roots.fetch(role), paths.last)
|
|
588
|
+
text = case fault
|
|
589
|
+
when 'ruby' then valid.sub('>= 3.2', '>= 99')
|
|
590
|
+
when 'dependency' then valid.sub('>= 13.0', '>= 99')
|
|
591
|
+
else valid
|
|
592
|
+
end
|
|
593
|
+
File.write(path, text)
|
|
594
|
+
cli = HaveAPI::Performance::CLI.new(['run', '--baseline-root', roots['baseline'], '--baseline-sha', samples['baseline_sha'],
|
|
595
|
+
'--candidate-root', roots['candidate'], '--candidate-sha', samples['candidate_sha'],
|
|
596
|
+
'--budgets', '/unused', '--output', '/unused'])
|
|
597
|
+
cli.send(:parse)
|
|
598
|
+
allow(cli).to receive(:verify_runtime)
|
|
599
|
+
allow(cli).to receive(:verify_source) { |name| { root: roots.fetch(name), sha: samples.fetch("#{name}_sha") } }
|
|
600
|
+
allow(cli).to receive(:ancestor?).and_return(true)
|
|
601
|
+
allow(HaveAPI::Performance::Release).to receive(:lineage).and_return(nil)
|
|
602
|
+
allow(cli).to receive(:child).and_raise(HaveAPI::Performance::ContractError, 'pilot boundary')
|
|
603
|
+
expect { cli.send(:run_samples, 'f' * 64) }.to raise_error(HaveAPI::Performance::ContractError,
|
|
604
|
+
fault == 'valid' ? 'pilot boundary' : /Benchmark Ruby|Benchmark dependencies/)
|
|
605
|
+
expect(cli).not_to have_received(:child) unless fault == 'valid'
|
|
606
|
+
File.write(path, valid)
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
end
|
|
476
610
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: haveapi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.31.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jakub Skokan
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.31.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.
|
|
39
|
+
version: 0.31.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: i18n
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -210,10 +210,13 @@ files:
|
|
|
210
210
|
- LICENSE.txt
|
|
211
211
|
- README.md
|
|
212
212
|
- Rakefile
|
|
213
|
+
- benchmarks/Gemfile
|
|
214
|
+
- benchmarks/Gemfile.lock
|
|
213
215
|
- benchmarks/documentation.rb
|
|
214
216
|
- benchmarks/endpoints.rb
|
|
215
217
|
- benchmarks/performance_budgets.json
|
|
216
218
|
- benchmarks/performance_compare.rb
|
|
219
|
+
- benchmarks/shell.nix
|
|
217
220
|
- doc/client-examples.md
|
|
218
221
|
- doc/create-client.md
|
|
219
222
|
- doc/hooks.erb
|
|
@@ -259,6 +262,7 @@ files:
|
|
|
259
262
|
- lib/haveapi/client_examples/rust_client.rb
|
|
260
263
|
- lib/haveapi/client_examples/ts_async_client.rb
|
|
261
264
|
- lib/haveapi/client_examples/ts_client.rb
|
|
265
|
+
- lib/haveapi/client_examples/ts_generated_client.rb
|
|
262
266
|
- lib/haveapi/common.rb
|
|
263
267
|
- lib/haveapi/context.rb
|
|
264
268
|
- lib/haveapi/documentation_clients.rb
|
|
@@ -366,6 +370,8 @@ files:
|
|
|
366
370
|
- spec/client_examples/rust_client_spec.rb
|
|
367
371
|
- spec/client_examples/structural_schema_spec.rb
|
|
368
372
|
- spec/client_examples/syntax_spec.rb
|
|
373
|
+
- spec/client_examples/ts_generated_client_spec.rb
|
|
374
|
+
- spec/client_examples/typescript_profiles_spec.rb
|
|
369
375
|
- spec/common_spec.rb
|
|
370
376
|
- spec/documentation/auth_filtering_spec.rb
|
|
371
377
|
- spec/documentation/client_examples_spec.rb
|