haveapi 0.30.2 → 0.31.1
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 +77 -21
- data/benchmarks/performance_compare.rb +204 -19
- data/benchmarks/shell.nix +21 -0
- data/benchmarks/spec/client_examples_spec.rb +216 -0
- data/doc/client-examples.md +88 -35
- data/doc/performance.md +163 -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 +185 -30
- data/spec/performance/release_lineage_spec.rb +53 -14
- data/spec/performance/runner_spec.rb +136 -0
- data/spec/performance/source_versions_spec.rb +168 -0
- data/test_support/all_client_performance_fixture.rb +84 -0
- metadata +12 -3
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'tmpdir'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require_relative '../../../../utils/performance_ci'
|
|
5
|
+
require_relative '../../benchmarks/endpoints'
|
|
6
|
+
require_relative '../../../../clients/ruby/lib/haveapi/client/version'
|
|
7
|
+
require_relative '../../../../clients/rust/lib/haveapi/rust_client/version'
|
|
8
|
+
require_relative 'support/samples'
|
|
9
|
+
|
|
10
|
+
describe HaveAPI::PerformanceCI::Campaign do
|
|
11
|
+
let(:root) { File.expand_path('../../../..', __dir__) }
|
|
12
|
+
let(:error) { HaveAPI::Performance::ContractError }
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
# These metadata fixtures evaluate real gemspecs without loading the optional
|
|
16
|
+
# Rust adapter. Ordinary server bundles do not include its dependencies; the
|
|
17
|
+
# real benchmark environment checks their installed identities separately.
|
|
18
|
+
allow(Gem::Specification).to receive(:find_by_name).and_call_original
|
|
19
|
+
{ 'fiddle' => '1.1.8', 'tomlrb' => '2.0.5' }.each do |name, version|
|
|
20
|
+
specification = Gem::Specification.new do |spec|
|
|
21
|
+
spec.name = name
|
|
22
|
+
spec.version = version
|
|
23
|
+
end
|
|
24
|
+
allow(Gem::Specification).to receive(:find_by_name).with(name).and_return(specification)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def source_contents(version)
|
|
29
|
+
paths = HaveAPI::Performance::Release.paths('quick')
|
|
30
|
+
actual = File.read(File.join(root, 'VERSION')).strip
|
|
31
|
+
paths.to_h do |path|
|
|
32
|
+
text = File.read(File.join(root, path))
|
|
33
|
+
text = "#{version}\n" if path == 'VERSION'
|
|
34
|
+
text = text.sub("VERSION = '#{actual}'", "VERSION = '#{version}'") if HaveAPI::Performance::Release::VERSIONS.has_key?(path)
|
|
35
|
+
text = text.sub("'haveapi-client', '~> #{actual}'", "'haveapi-client', '~> #{version}'") if path.end_with?('.gemspec')
|
|
36
|
+
[path, text]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def write_source(directory, contents)
|
|
41
|
+
contents.each do |path, text|
|
|
42
|
+
FileUtils.mkdir_p(File.dirname(File.join(directory, path)))
|
|
43
|
+
File.write(File.join(directory, path), text)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def prepare(baseline, candidate)
|
|
48
|
+
Dir.mktmpdir('haveapi-own-versions-') do |directory|
|
|
49
|
+
roots = { baseline: File.join(directory, 'baseline'), candidate: File.join(directory, 'candidate') }
|
|
50
|
+
write_source(roots[:baseline], baseline)
|
|
51
|
+
write_source(roots[:candidate], candidate)
|
|
52
|
+
campaign = described_class.new(repository: nil, selection: { 'profile' => 'quick' }, evidence: directory, sources: directory)
|
|
53
|
+
campaign.send(:verify_dependencies, roots)
|
|
54
|
+
yield roots if block_given?
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
%w[0.30.0 0.31.0].each do |baseline|
|
|
59
|
+
it "accepts synchronized #{baseline} to 0.31.1 metadata during preparation" do
|
|
60
|
+
expect { prepare(source_contents(baseline), source_contents('0.31.1')) }.not_to raise_error
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "retains and validates the live worker graph for #{baseline} to 0.31.1" do
|
|
64
|
+
prepare(source_contents(baseline), source_contents('0.31.1')) do |roots|
|
|
65
|
+
graphs = roots.map do |role, directory|
|
|
66
|
+
version = role == :baseline ? baseline : '0.31.1'
|
|
67
|
+
%w[HaveAPI::VERSION HaveAPI::Client::VERSION HaveAPI::RustClient::VERSION].each { |constant| stub_const(constant, version) }
|
|
68
|
+
inputs = JSON.parse(JSON.generate(HaveAPI::Performance::Worker.inputs(directory, 'quick', benchmark: true)))
|
|
69
|
+
hashes = source_contents(version).transform_values { |text| Digest::SHA256.hexdigest(text) }
|
|
70
|
+
expect do
|
|
71
|
+
HaveAPI::Performance.validate_input_receipts(inputs, { 'files_sha256' => hashes }, profile: 'quick', lineage: nil)
|
|
72
|
+
end.not_to raise_error
|
|
73
|
+
HaveAPI::Performance::Release.graph(inputs)
|
|
74
|
+
end
|
|
75
|
+
expect(graphs.first).to eq(graphs.last)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "replays the synchronized #{baseline} to 0.31.1 graph" do
|
|
80
|
+
samples, budgets = replay_samples(baseline)
|
|
81
|
+
expect(compare(samples, budgets).fetch('status')).to eq('pass')
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def replay_samples(baseline = '0.31.0')
|
|
86
|
+
samples, budgets = PerformanceSamples.fixture
|
|
87
|
+
samples.fetch('sources').each do |role, source|
|
|
88
|
+
contents = source_contents(role == 'baseline' ? baseline : '0.31.1').slice(*HaveAPI::Performance::SourceVersions::PATHS)
|
|
89
|
+
receipt = HaveAPI::Performance::SourceVersions.capture(contents)
|
|
90
|
+
source['files_sha256'].merge!(receipt.fetch('raw_sha256'))
|
|
91
|
+
samples.fetch('observations').each_value do |observation|
|
|
92
|
+
next unless observation.fetch('source').fetch('root') == source.fetch('root')
|
|
93
|
+
|
|
94
|
+
inputs = Marshal.load(Marshal.dump(observation.fetch('inputs')))
|
|
95
|
+
inputs['manifests'].merge!(receipt.fetch('normalized_sha256'))
|
|
96
|
+
inputs['source_versions'] = receipt
|
|
97
|
+
observation['inputs'] = inputs
|
|
98
|
+
samples['inputs'] = HaveAPI::Performance::Release.graph(inputs)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
[samples, JSON.parse(budgets)]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def compare(samples, budgets)
|
|
105
|
+
HaveAPI::Performance::Comparator.new(samples, budgets, budget_sha256: samples.fetch('budget_sha256')).compare
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'rejects external dependency and other unnormalized input edits in preparation' do
|
|
109
|
+
%w[Gemfile .ruby-version servers/ruby/haveapi.gemspec].each do |path|
|
|
110
|
+
changed = source_contents('0.31.1')
|
|
111
|
+
changed[path] = path == '.ruby-version' ? "3.0.0\n" : "#{changed[path]}\n# unrelated change\n"
|
|
112
|
+
expect { prepare(source_contents('0.31.0'), changed) }.to raise_error(HaveAPI::PerformanceCI::Error, /Dependency inputs differ/)
|
|
113
|
+
end
|
|
114
|
+
changed = source_contents('0.31.1')
|
|
115
|
+
changed['servers/ruby/haveapi.gemspec'] = changed['servers/ruby/haveapi.gemspec'].sub("'activesupport', '>= 7.1'", "'activesupport', '>= 999.0'")
|
|
116
|
+
expect { prepare(source_contents('0.31.0'), changed) }.to raise_error(error, /Benchmark dependencies do not satisfy/)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'rejects malformed, missing, duplicate and unsynchronized own declarations' do
|
|
120
|
+
original = source_contents('0.31.1').slice(*HaveAPI::Performance::SourceVersions::PATHS)
|
|
121
|
+
changes = [
|
|
122
|
+
['VERSION', "0.31.01\n"], ['VERSION', " 0.31.1\n"],
|
|
123
|
+
['servers/ruby/lib/haveapi/version.rb', original['servers/ruby/lib/haveapi/version.rb'].sub('0.31.1', '0.31.0')],
|
|
124
|
+
['servers/ruby/lib/haveapi/version.rb', "VERSION = '0.31.1'.freeze\nVERSION = '0.31.1'.freeze\n"],
|
|
125
|
+
['servers/ruby/haveapi.gemspec', original['servers/ruby/haveapi.gemspec'].sub('~> 0.31.1', '~> 0.31.0')],
|
|
126
|
+
['servers/ruby/haveapi.gemspec', original['servers/ruby/haveapi.gemspec'].sub("~> 0.31.1'", "~> 0.31.1', '< 0.32'")],
|
|
127
|
+
['servers/ruby/haveapi.gemspec', "#{original['servers/ruby/haveapi.gemspec']}s.add_dependency 'haveapi-client', '~> 0.31.1'\n"],
|
|
128
|
+
['servers/ruby/haveapi.gemspec', original['servers/ruby/haveapi.gemspec'].sub('HaveAPI::VERSION', "'0.31.0'")],
|
|
129
|
+
['clients/ruby/haveapi-client.gemspec', original['clients/ruby/haveapi-client.gemspec'].sub("'haveapi-client'", "'other-client'")]
|
|
130
|
+
]
|
|
131
|
+
changes.each do |path, text|
|
|
132
|
+
expect { HaveAPI::Performance::SourceVersions.capture(original.merge(path => text)) }.to raise_error(error)
|
|
133
|
+
end
|
|
134
|
+
expect { HaveAPI::Performance::SourceVersions.capture(original.except('VERSION')) }.to raise_error(error)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'rejects loaded versions that do not match the captured source metadata' do
|
|
138
|
+
Dir.mktmpdir('haveapi-own-worker-') do |directory|
|
|
139
|
+
write_source(directory, source_contents('0.31.99'))
|
|
140
|
+
expect { HaveAPI::Performance::Worker.inputs(directory, 'quick', benchmark: true) }.to raise_error(error, /Wrong loaded local package version/)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it 'rejects tampered, stripped, conflicting or unbound receipts and changed runtime/lock inputs on replay' do
|
|
145
|
+
%w[raw normalized content stripped conflicting source runtime lock].each do |fault|
|
|
146
|
+
samples, budgets = replay_samples
|
|
147
|
+
observation = samples.fetch('observations').fetch('candidate')
|
|
148
|
+
inputs = observation.fetch('inputs')
|
|
149
|
+
receipt = inputs.fetch('source_versions')
|
|
150
|
+
case fault
|
|
151
|
+
when 'raw' then receipt['raw_sha256']['VERSION'] = 'f' * 64
|
|
152
|
+
when 'normalized' then receipt['normalized_sha256']['VERSION'] = 'f' * 64
|
|
153
|
+
when 'content' then receipt['contents']['VERSION'] = "0.31.2\n"
|
|
154
|
+
when 'stripped' then inputs.delete('source_versions')
|
|
155
|
+
when 'conflicting' then inputs['release_versions'] = {}
|
|
156
|
+
when 'source' then samples['sources']['candidate']['files_sha256']['VERSION'] = 'f' * 64
|
|
157
|
+
when 'runtime' then observation['environment'] = { 'ruby' => 'changed' }
|
|
158
|
+
when 'lock' then inputs['locks']['Gemfile.lock'] = 'f' * 64
|
|
159
|
+
end
|
|
160
|
+
expect { compare(samples, budgets) }.to raise_error(error), fault
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it 'continues to replay legacy raw manifests without own-version receipts' do
|
|
165
|
+
samples, budgets = PerformanceSamples.fixture
|
|
166
|
+
expect(compare(samples, JSON.parse(budgets)).fetch('status')).to eq('pass')
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require_relative 'documentation_performance_fixture'
|
|
2
|
+
|
|
3
|
+
module HaveAPI
|
|
4
|
+
# Scalar samples deliberately stay inside every generator's supported subset.
|
|
5
|
+
# The historical timing fixture and its calibrated workloads stay separate.
|
|
6
|
+
module AllClientPerformanceFixture
|
|
7
|
+
class TokenConfig < Authentication::Token::Config
|
|
8
|
+
# Keep the saved JSON SDK description and live provider value identical.
|
|
9
|
+
query_parameter '_auth_token'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class OAuthConfig < Authentication::OAuth2::Config
|
|
13
|
+
def base_url
|
|
14
|
+
'http://example.test'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.build(resources:)
|
|
19
|
+
definitions = Module.new
|
|
20
|
+
resources.times do |index|
|
|
21
|
+
resource = Class.new(HaveAPI::Resource)
|
|
22
|
+
definitions.const_set("Resource#{index}", resource)
|
|
23
|
+
resource.resource_name = "Resource#{index}"
|
|
24
|
+
resource.version 1
|
|
25
|
+
resource.route "resources#{index}"
|
|
26
|
+
%w[Public Protected].each { |name| add_action(resource, name) }
|
|
27
|
+
DocumentationPerformanceFixture.send(:add_admin_action, resource) if index == 0
|
|
28
|
+
end
|
|
29
|
+
server = HaveAPI::Server.new(definitions)
|
|
30
|
+
server.use_version(1)
|
|
31
|
+
server.default_version = 1
|
|
32
|
+
server.auth_chain << DocumentationPerformanceFixture::BasicProvider
|
|
33
|
+
server.auth_chain << Authentication::Token.with_config(TokenConfig)
|
|
34
|
+
server.auth_chain << Authentication::OAuth2.with_config(OAuthConfig)
|
|
35
|
+
server.documentation_clients(1,
|
|
36
|
+
go: { import_path: 'example.test/performance/client' },
|
|
37
|
+
rust: { crate_name: 'performance_client' },
|
|
38
|
+
typescript: { package_name: 'performance-client' })
|
|
39
|
+
server.mount('/benchmark/api/')
|
|
40
|
+
server
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.add_action(resource, name)
|
|
44
|
+
resource.define_action(name) do
|
|
45
|
+
route name.downcase
|
|
46
|
+
http_method :post
|
|
47
|
+
auth name == 'Protected'
|
|
48
|
+
input(:hash) do
|
|
49
|
+
string :value
|
|
50
|
+
bool :enabled
|
|
51
|
+
integer :count
|
|
52
|
+
string :admin_field, default: 'ADMIN_ONLY_DEFAULT'
|
|
53
|
+
end
|
|
54
|
+
output(:hash) do
|
|
55
|
+
string :marker
|
|
56
|
+
string :admin_field
|
|
57
|
+
end
|
|
58
|
+
authorize do |user|
|
|
59
|
+
if user && user.login != 'admin'
|
|
60
|
+
input blacklist: [:admin_field]
|
|
61
|
+
output blacklist: [:admin_field]
|
|
62
|
+
end
|
|
63
|
+
allow
|
|
64
|
+
end
|
|
65
|
+
example 'Success' do
|
|
66
|
+
request(value: '', enabled: false, count: 0)
|
|
67
|
+
response(marker: 'PUBLIC_SAMPLE')
|
|
68
|
+
end
|
|
69
|
+
example 'Failure' do
|
|
70
|
+
request(value: '', enabled: false, count: 0)
|
|
71
|
+
status false
|
|
72
|
+
message 'Expected failure'
|
|
73
|
+
errors(value: ['fixture error'])
|
|
74
|
+
end
|
|
75
|
+
example 'Admin' do
|
|
76
|
+
authorize { |user| user.login == 'admin' }
|
|
77
|
+
request(admin_field: 'ADMIN_ONLY_REQUEST')
|
|
78
|
+
response(admin_field: 'ADMIN_ONLY_RESPONSE')
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
private_class_method :add_action
|
|
83
|
+
end
|
|
84
|
+
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.1
|
|
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.1
|
|
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.1
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: i18n
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -210,10 +210,14 @@ 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
|
|
220
|
+
- benchmarks/spec/client_examples_spec.rb
|
|
217
221
|
- doc/client-examples.md
|
|
218
222
|
- doc/create-client.md
|
|
219
223
|
- doc/hooks.erb
|
|
@@ -259,6 +263,7 @@ files:
|
|
|
259
263
|
- lib/haveapi/client_examples/rust_client.rb
|
|
260
264
|
- lib/haveapi/client_examples/ts_async_client.rb
|
|
261
265
|
- lib/haveapi/client_examples/ts_client.rb
|
|
266
|
+
- lib/haveapi/client_examples/ts_generated_client.rb
|
|
262
267
|
- lib/haveapi/common.rb
|
|
263
268
|
- lib/haveapi/context.rb
|
|
264
269
|
- lib/haveapi/documentation_clients.rb
|
|
@@ -366,6 +371,8 @@ files:
|
|
|
366
371
|
- spec/client_examples/rust_client_spec.rb
|
|
367
372
|
- spec/client_examples/structural_schema_spec.rb
|
|
368
373
|
- spec/client_examples/syntax_spec.rb
|
|
374
|
+
- spec/client_examples/ts_generated_client_spec.rb
|
|
375
|
+
- spec/client_examples/typescript_profiles_spec.rb
|
|
369
376
|
- spec/common_spec.rb
|
|
370
377
|
- spec/documentation/auth_filtering_spec.rb
|
|
371
378
|
- spec/documentation/client_examples_spec.rb
|
|
@@ -390,6 +397,7 @@ files:
|
|
|
390
397
|
- spec/performance/json_activation_spec.rb
|
|
391
398
|
- spec/performance/release_lineage_spec.rb
|
|
392
399
|
- spec/performance/runner_spec.rb
|
|
400
|
+
- spec/performance/source_versions_spec.rb
|
|
393
401
|
- spec/performance/sqlite_spec.rb
|
|
394
402
|
- spec/performance/support/live_worker.rb
|
|
395
403
|
- spec/performance/support/samples.rb
|
|
@@ -406,6 +414,7 @@ files:
|
|
|
406
414
|
- spec/validators/length_spec.rb
|
|
407
415
|
- spec/validators/numericality_spec.rb
|
|
408
416
|
- spec/validators/presence_spec.rb
|
|
417
|
+
- test_support/all_client_performance_fixture.rb
|
|
409
418
|
- test_support/client_test_api.rb
|
|
410
419
|
- test_support/client_test_server.rb
|
|
411
420
|
- test_support/documentation_performance_fixture.rb
|