Skip to content

Commit 8a35f3d

Browse files
authored
feat(storage): Add retry conformance test (googleapis#18230)
1 parent 50f0448 commit 8a35f3d

10 files changed

Lines changed: 1171 additions & 65 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
on:
2+
push:
3+
branches:
4+
- main
5+
paths:
6+
- 'google-cloud-storage/**'
7+
pull_request:
8+
paths:
9+
- 'google-cloud-storage/**'
10+
name: Run Storage retry conformance tests against service emulator
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
defaults:
15+
run:
16+
shell: bash
17+
working-directory: google-cloud-storage
18+
19+
services:
20+
emulator:
21+
image: gcr.io/cloud-devrel-public-resources/storage-testbench:latest
22+
ports:
23+
- 9000:9000
24+
25+
steps:
26+
- uses: actions/checkout@v3
27+
- uses: actions/setup-ruby@v1
28+
with:
29+
ruby-version: '2.6'
30+
- run: ruby --version
31+
- run: bundle install
32+
- run: bundle exec rake conformance
33+
env:
34+
STORAGE_EMULATOR_HOST: localhost:9000

google-cloud-storage/Rakefile

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ require "rake/testtask"
88
desc "Run tests."
99
Rake::TestTask.new do |t|
1010
t.libs << "test"
11-
t.test_files = FileList["test/**/*_test.rb"]
11+
t.test_files = FileList["test/**/*_test.rb"].exclude(/conformance/)
12+
t.warning = false
13+
end
14+
15+
desc "Run conformance tests"
16+
Rake::TestTask.new :conformance do |t|
17+
t.libs << "test"
18+
t.test_files = FileList["test/**/*conformance_test.rb"]
1219
t.warning = false
1320
end
1421

@@ -26,6 +33,14 @@ namespace :test do
2633
end
2734
end
2835

36+
# Conformance Tests
37+
namespace :conformance do
38+
desc "Run storage conformance tests."
39+
task :run do
40+
Rake::Task[:conformance].invoke
41+
end
42+
end
43+
2944
# Acceptance tests
3045
desc "Run the storage acceptance tests."
3146
task :acceptance, :project, :keyfile do |t, args|
@@ -161,6 +176,8 @@ task :ci do
161176
Rake::Task[:doctest].invoke
162177
header "google-cloud-storage test", "*"
163178
Rake::Task[:test].invoke
179+
header "google-cloud-storage conformance", "*"
180+
Rake::Task[:conformance].invoke
164181
end
165182
namespace :ci do
166183
desc "Run the CI build, with acceptance tests."

google-cloud-storage/conformance/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,9 @@ The output directory is the same is the input directory, so after successful com
1414

1515
## Running the tests
1616

17-
The conformance tests are included in the ordinary unit test suite. In the `google-cloud-storage` directory, run `rake test`.
17+
The conformance tests are included in a separate unit test suite. In the `google-cloud-storage` directory, run `rake conformance`.
18+
19+
Because the conformance tests are dynamically generated at run time, working with them is more difficult than working with hand-written tests. If you need to execute one or more of these tests in isolation, you can do so by placing the `focus` keyword just above one of the calls to `define_method`. This will isolate a subset of the conformance tests.
20+
21+
Note: Retry conformance tests run against [storage-testbench](https://github.com/googleapis/storage-testbench) emulator instead of the production endpoint. You need to run the emulator first before running the tests. To start the emulator in your local, spin up a docker container with testbench [docker image](gcr.io/cloud-devrel-public-resources/storage-testbench:latest) and with port `9000`.
22+

google-cloud-storage/conformance/v1/proto/google/cloud/conformance/storage/v1/tests.proto

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import "google/protobuf/timestamp.proto";
2121
option csharp_namespace = "Google.Cloud.Storage.V1.Tests.Conformance";
2222
option java_package = "com.google.cloud.conformance.storage.v1";
2323
option java_multiple_files = true;
24+
option go_package = "google/cloud/conformance/storage/v1";
2425

2526
message TestFile {
2627
repeated SigningV4Test signing_v4_tests = 1;
@@ -60,21 +61,95 @@ message PolicyConditions {
6061
repeated string startsWith = 2;
6162
}
6263

64+
// Specification documentation is located at:
65+
// https://cloud.google.com/storage/docs/authentication/signatures
66+
6367
message PolicyInput {
68+
// http or https
6469
string scheme = 1;
6570
UrlStyle urlStyle = 2;
6671
string bucketBoundHostname = 3;
6772
string bucket = 4;
6873
string object = 5;
6974
int32 expiration = 6;
7075
google.protobuf.Timestamp timestamp = 7;
76+
/*
77+
fields with strict equivalence which are added into
78+
PolicyOutput.expectedDecodedPolicy to generate the
79+
signature.
80+
81+
Expectations
82+
83+
E.1: Order them in lexigraphical order so it's the
84+
signature can be verified across different language
85+
implementations.
86+
87+
*/
7188
map<string, string> fields = 8;
7289
PolicyConditions conditions = 9;
7390
}
7491

7592
message PolicyOutput {
7693
string url = 1;
7794
map<string, string> fields = 2;
95+
/*
96+
Expectations
97+
98+
E.1: PolicyInput.fields must be prepended to form expectedDecodedPolicy
99+
for consistent result across languages. Ordering doesn't matter to the
100+
service but the decision is made to make it easier to conform implementations
101+
in implementation.
102+
103+
Example:
104+
105+
# Step 1
106+
107+
PolicyInput.fields has:
108+
{
109+
"content-disposition":"attachment; filename=\"~._-%=/é0Aa\"",
110+
"content-encoding":"gzip",
111+
"content-type":"text/plain",
112+
"success_action_redirect":"http://www.google.com/"
113+
}
114+
115+
# Step 2
116+
117+
The expectedDecodedPolicy before prepending the PolicyInput.fields
118+
would look like this:
119+
120+
{
121+
"conditions":[
122+
...prepend here in the same order provided in PolicyInput.fields...
123+
{"bucket":"bucket-name"},
124+
{"key":"test-object"},
125+
{"x-goog-date":"20200123T043530Z"},
126+
{"x-goog-credential":"test-iam-credentials@dummy-project-id.iam.gserviceaccount.com/20200123/auto/storage/goog4_request"},
127+
{"x-goog-algorithm":"GOOG4-RSA-SHA256"}
128+
],
129+
"expiration":"2020-01-23T04:35:40Z"
130+
}
131+
132+
# Step 3
133+
134+
Then expectedDecodedPolicy should prepends PolicyInput.fields in
135+
the same order to PolicyOutput.expectedDecodedPolicy `conditions` key.
136+
137+
{
138+
"conditions":[
139+
{"content-disposition":"attachment; filename=\"~._-%=/é0Aa\""},
140+
{"content-encoding":"gzip"},
141+
{"content-type":"text/plain"},
142+
{"success_action_redirect":"http://www.google.com/"},
143+
{"bucket":"bucket-name"},
144+
{"key":"test-object"},
145+
{"x-goog-date":"20200123T043530Z"},
146+
{"x-goog-credential":"test-iam-credentials@dummy-project-id.iam.gserviceaccount.com/20200123/auto/storage/goog4_request"},
147+
{"x-goog-algorithm":"GOOG4-RSA-SHA256"}
148+
],
149+
"expiration":"2020-01-23T04:35:40Z"
150+
}
151+
*/
152+
78153
string expectedDecodedPolicy = 3;
79154
}
80155

@@ -83,3 +158,60 @@ message PostPolicyV4Test {
83158
PolicyInput policyInput = 2;
84159
PolicyOutput policyOutput = 3;
85160
}
161+
162+
/*
163+
------------------------------------------------------------------------------
164+
Data types for retry conformance tests
165+
------------------------------------------------------------------------------
166+
*/
167+
168+
message RetryTests {
169+
repeated RetryTest retryTests = 1;
170+
}
171+
172+
// A list of instructions to send as headers to the GCS emulator. Each
173+
// instruction will force a specified failure for that request.
174+
message InstructionList {
175+
repeated string instructions = 1;
176+
}
177+
178+
// Test resources that are necessary for a method call. For example,
179+
// storage.objects.get would require BUCKET and OBJECT.
180+
enum Resource {
181+
BUCKET = 0;
182+
OBJECT = 1;
183+
NOTIFICATION = 2;
184+
HMAC_KEY = 3;
185+
}
186+
187+
// A particular storage API method and required resources in order to test it.
188+
// Methods must be implemented in tests for each language.
189+
message Method {
190+
string name = 1; // e.g. storage.objects.get
191+
repeated Resource resources = 2;
192+
string group = 3; // e.g. storage.resumable.upload
193+
}
194+
195+
// Schema for a retry test, corresponding to a single scenario from the design
196+
// doc.
197+
message RetryTest {
198+
// Scenario number
199+
int32 id = 1;
200+
201+
// Human-readable description of the test case.
202+
string description = 2;
203+
204+
// list of emulator instruction sets.
205+
repeated InstructionList cases = 3;
206+
207+
// List of API methods to be tested.
208+
repeated Method methods = 4;
209+
210+
// Whether a precondition is provided (for conditionally-idempotent methods
211+
// only).
212+
bool preconditionProvided = 5;
213+
214+
// Whether we expect the method calls to eventually succeed after the client
215+
// library retries.
216+
bool expectSuccess = 6;
217+
}

0 commit comments

Comments
 (0)