├── .github
└── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── RELEASING.md
├── SECURITY.md
├── bigquery
└── bqiface
│ ├── adapters.go
│ ├── bqiface_test.go
│ ├── doc.go
│ ├── examples_test.go
│ ├── interfaces.go
│ └── structs.go
├── datastore
└── dsiface
│ ├── README.md
│ ├── adapters.go
│ ├── doc.go
│ ├── dsiface_test.go
│ ├── examples_test.go
│ └── interfaces.go
├── doc.go
├── examples_test.go
├── go.mod
├── go.sum
├── internal
└── kokoro
│ ├── test.sh
│ ├── trampoline.sh
│ └── vet.sh
├── pubsub
└── psiface
│ ├── adapters.go
│ ├── doc.go
│ ├── examples_test.go
│ ├── interfaces.go
│ ├── psiface_test.go
│ └── structs.go
├── storage
└── stiface
│ ├── adapters.go
│ ├── doc.go
│ ├── examples_test.go
│ ├── interfaces.go
│ └── stiface_test.go
└── tools.go
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Default owner for all directories not owned by others
2 | * @googleapis/yoshi-go-admins
3 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project,
4 | and in the interest of fostering an open and welcoming community,
5 | we pledge to respect all people who contribute through reporting issues,
6 | posting feature requests, updating documentation,
7 | submitting pull requests or patches, and other activities.
8 |
9 | We are committed to making participation in this project
10 | a harassment-free experience for everyone,
11 | regardless of level of experience, gender, gender identity and expression,
12 | sexual orientation, disability, personal appearance,
13 | body size, race, ethnicity, age, religion, or nationality.
14 |
15 | Examples of unacceptable behavior by participants include:
16 |
17 | * The use of sexualized language or imagery
18 | * Personal attacks
19 | * Trolling or insulting/derogatory comments
20 | * Public or private harassment
21 | * Publishing other's private information,
22 | such as physical or electronic
23 | addresses, without explicit permission
24 | * Other unethical or unprofessional conduct.
25 |
26 | Project maintainers have the right and responsibility to remove, edit, or reject
27 | comments, commits, code, wiki edits, issues, and other contributions
28 | that are not aligned to this Code of Conduct.
29 | By adopting this Code of Conduct,
30 | project maintainers commit themselves to fairly and consistently
31 | applying these principles to every aspect of managing this project.
32 | Project maintainers who do not follow or enforce the Code of Conduct
33 | may be permanently removed from the project team.
34 |
35 | This code of conduct applies both within project spaces and in public spaces
36 | when an individual is representing the project or its community.
37 |
38 | Instances of abusive, harassing, or otherwise unacceptable behavior
39 | may be reported by opening an issue
40 | or contacting one or more of the project maintainers.
41 |
42 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
43 | available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
44 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google.com/conduct/).
29 |
30 | ## Testing
31 |
32 | ### storage/psiface
33 |
34 | Before running the tests, set the PSIFACE_TOPIC environment variable to the
35 | name of a topic for which you have publish permissions and the ability to
36 | create, consume and delete subscriptions.
37 |
38 | ### storage/stiface
39 |
40 | Before running the tests, set the STIFACE_BUCKET environment variable to the
41 | name of a bucket you have read/write access to.
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Testing Support for the Google Cloud Client Libraries for Go
2 |
3 | ## Status
4 |
5 | This project is no longer being maintained. If you would like to see patternes you can use
6 | to test against the Go client libraries please see the links below:
7 |
8 | - [Test cloud.google.com/go clients](https://github.com/googleapis/google-cloud-go/blob/master/testing.md).
9 | - [Test google.golang.org/api clients](https://github.com/googleapis/google-api-go-client/blob/master/testing.md).
10 |
11 | ## Install
12 |
13 | ```
14 | go get github.com/googleapis/google-cloud-go-testing
15 | ```
16 |
17 | This repository contains code that can help you test against the [Cloud Client
18 | Libraries for Go](https://github.com/GoogleCloudPlatform/google-cloud-go).
19 |
20 | If you plan to use this code for mocks, we recommend you read [Don't Overuse
21 | Mocks](https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html) first.
22 |
23 | **NOTE:** This code has alpha status. It may be subject to backwards-incompatible changes.
24 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | # Releasing
2 |
3 | This repository does not have releases. Users should rely on semver
4 | pseudo-versions in their go.mod files.
5 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | To report a security issue, please use [g.co/vulnz](https://g.co/vulnz).
4 |
5 | The Google Security Team will respond within 5 working days of your report on g.co/vulnz.
6 |
7 | We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue.
8 |
--------------------------------------------------------------------------------
/bigquery/bqiface/adapters.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package bqiface
16 |
17 | import (
18 | "context"
19 |
20 | "cloud.google.com/go/bigquery"
21 | )
22 |
23 | func AdaptClient(c *bigquery.Client) Client {
24 | return client{c}
25 | }
26 |
27 | type (
28 | client struct{ *bigquery.Client }
29 | copier struct{ *bigquery.Copier }
30 | dataset struct{ *bigquery.Dataset }
31 | datasetIterator struct{ *bigquery.DatasetIterator }
32 | extractor struct{ *bigquery.Extractor }
33 | job struct{ *bigquery.Job }
34 | jobIterator struct{ *bigquery.JobIterator }
35 | loader struct{ *bigquery.Loader }
36 | query struct{ *bigquery.Query }
37 | rowIterator struct{ *bigquery.RowIterator }
38 | table struct{ *bigquery.Table }
39 | tableIterator struct{ *bigquery.TableIterator }
40 | uploader struct{ *bigquery.Uploader }
41 | )
42 |
43 | func (client) embedToIncludeNewMethods() {}
44 | func (copier) embedToIncludeNewMethods() {}
45 | func (dataset) embedToIncludeNewMethods() {}
46 | func (extractor) embedToIncludeNewMethods() {}
47 | func (job) embedToIncludeNewMethods() {}
48 | func (jobIterator) embedToIncludeNewMethods() {}
49 | func (loader) embedToIncludeNewMethods() {}
50 | func (query) embedToIncludeNewMethods() {}
51 | func (rowIterator) embedToIncludeNewMethods() {}
52 | func (table) embedToIncludeNewMethods() {}
53 | func (datasetIterator) embedToIncludeNewMethods() {}
54 | func (tableIterator) embedToIncludeNewMethods() {}
55 | func (uploader) embedToIncludeNewMethods() {}
56 |
57 | func (c client) Location() string { return c.Client.Location }
58 | func (c client) SetLocation(s string) { c.Client.Location = s }
59 | func (c client) Close() error { return c.Client.Close() }
60 | func (c client) Dataset(id string) Dataset { return dataset{c.Client.Dataset(id)} }
61 | func (c client) Jobs(ctx context.Context) JobIterator { return jobIterator{c.Client.Jobs(ctx)} }
62 | func (c client) Query(s string) Query { return query{c.Client.Query(s)} }
63 |
64 | func (c client) DatasetInProject(p, d string) Dataset {
65 | return dataset{c.Client.DatasetInProject(p, d)}
66 | }
67 |
68 | func (c client) Datasets(ctx context.Context) DatasetIterator {
69 | return datasetIterator{c.Client.Datasets(ctx)}
70 | }
71 |
72 | func (c client) DatasetsInProject(ctx context.Context, p string) DatasetIterator {
73 | return datasetIterator{c.Client.DatasetsInProject(ctx, p)}
74 | }
75 |
76 | func (c client) JobFromID(ctx context.Context, id string) (Job, error) {
77 | return adaptJob(c.Client.JobFromID(ctx, id))
78 | }
79 |
80 | func (c client) JobFromIDLocation(ctx context.Context, id, location string) (Job, error) {
81 | return adaptJob(c.Client.JobFromIDLocation(ctx, id, location))
82 | }
83 |
84 | func (c copier) JobIDConfig() *bigquery.JobIDConfig { return &c.Copier.JobIDConfig }
85 |
86 | func (c copier) SetCopyConfig(cc CopyConfig) {
87 | c.Copier.CopyConfig = cc.CopyConfig
88 | for _, t := range cc.Srcs {
89 | c.Copier.CopyConfig.Srcs = append(c.Copier.CopyConfig.Srcs, t.(table).Table)
90 | }
91 | c.Copier.CopyConfig.Dst = cc.Dst.(table).Table
92 | }
93 |
94 | func (c copier) Run(ctx context.Context) (Job, error) {
95 | return adaptJob(c.Copier.Run(ctx))
96 | }
97 |
98 | func (d dataset) ProjectID() string { return d.Dataset.ProjectID }
99 | func (d dataset) DatasetID() string { return d.Dataset.DatasetID }
100 | func (d dataset) Delete(ctx context.Context) error { return d.Dataset.Delete(ctx) }
101 | func (d dataset) DeleteWithContents(ctx context.Context) error {
102 | return d.Dataset.DeleteWithContents(ctx)
103 | }
104 | func (d dataset) Table(id string) Table { return table{d.Dataset.Table(id)} }
105 | func (d dataset) Tables(ctx context.Context) TableIterator {
106 | return tableIterator{d.Dataset.Tables(ctx)}
107 | }
108 |
109 | func (d dataset) Create(ctx context.Context, dm *DatasetMetadata) error {
110 | return d.Dataset.Create(ctx, dm.toBQ())
111 | }
112 |
113 | func (d dataset) Metadata(ctx context.Context) (*DatasetMetadata, error) {
114 | m, err := d.Dataset.Metadata(ctx)
115 | return datasetMetadataFromBQ(m), err
116 | }
117 |
118 | func (d dataset) Update(ctx context.Context, dm DatasetMetadataToUpdate, etag string) (*DatasetMetadata, error) {
119 | m, err := d.Dataset.Update(ctx, dm.toBQ(), etag)
120 | if err != nil {
121 | return nil, err
122 | }
123 | return datasetMetadataFromBQ(m), nil
124 | }
125 |
126 | func (di datasetIterator) SetListHidden(b bool) { di.DatasetIterator.ListHidden = b }
127 | func (di datasetIterator) SetFilter(s string) { di.DatasetIterator.Filter = s }
128 | func (di datasetIterator) SetProjectID(s string) { di.DatasetIterator.ProjectID = s }
129 |
130 | func (di datasetIterator) Next() (Dataset, error) {
131 | ds, err := di.DatasetIterator.Next()
132 | if err != nil {
133 | return nil, err
134 | }
135 | return dataset{ds}, nil
136 | }
137 |
138 | func (e extractor) JobIDConfig() *bigquery.JobIDConfig { return &e.Extractor.JobIDConfig }
139 |
140 | func (e extractor) SetExtractConfig(c ExtractConfig) {
141 | e.Extractor.ExtractConfig = c.ExtractConfig
142 | e.Extractor.ExtractConfig.Src = c.Src.(table).Table
143 | }
144 |
145 | func (e extractor) Run(ctx context.Context) (Job, error) {
146 | return adaptJob(e.Extractor.Run(ctx))
147 | }
148 |
149 | func (j job) Status(ctx context.Context) (*bigquery.JobStatus, error) { return j.Job.Status(ctx) }
150 | func (j job) LastStatus() *bigquery.JobStatus { return j.Job.LastStatus() }
151 | func (j job) Cancel(ctx context.Context) error { return j.Job.Cancel(ctx) }
152 | func (j job) Wait(ctx context.Context) (*bigquery.JobStatus, error) { return j.Job.Wait(ctx) }
153 |
154 | func (j job) Read(ctx context.Context) (RowIterator, error) {
155 | r, err := j.Job.Read(ctx)
156 | if err != nil {
157 | return nil, err
158 | }
159 | return rowIterator{r}, nil
160 | }
161 |
162 | func (j jobIterator) SetProjectID(s string) { j.JobIterator.ProjectID = s }
163 | func (j jobIterator) SetAllUsers(b bool) { j.JobIterator.AllUsers = b }
164 | func (j jobIterator) SetState(s bigquery.State) { j.JobIterator.State = s }
165 |
166 | func (j jobIterator) Next() (Job, error) {
167 | return adaptJob(j.JobIterator.Next())
168 | }
169 |
170 | func (l loader) JobIDConfig() *bigquery.JobIDConfig { return &l.Loader.JobIDConfig }
171 |
172 | func (l loader) SetLoadConfig(c LoadConfig) {
173 | l.Loader.LoadConfig = c.LoadConfig
174 | l.Loader.LoadConfig.Dst = c.Dst.(table).Table
175 | }
176 |
177 | func (l loader) Run(ctx context.Context) (Job, error) {
178 | return adaptJob(l.Loader.Run(ctx))
179 | }
180 |
181 | func (q query) JobIDConfig() *bigquery.JobIDConfig { return &q.Query.JobIDConfig }
182 | func (q query) Run(ctx context.Context) (Job, error) { return adaptJob(q.Query.Run(ctx)) }
183 |
184 | func (q query) Read(ctx context.Context) (RowIterator, error) {
185 | r, err := q.Query.Read(ctx)
186 | if err != nil {
187 | return nil, err
188 | }
189 | return rowIterator{r}, nil
190 | }
191 |
192 | func (q query) SetQueryConfig(c QueryConfig) {
193 | q.Query.QueryConfig = c.QueryConfig
194 | if c.Dst != nil {
195 | q.Query.QueryConfig.Dst = c.Dst.(table).Table
196 | }
197 | }
198 |
199 | func (r rowIterator) SetStartIndex(i uint64) { r.RowIterator.StartIndex = i }
200 | func (r rowIterator) Schema() bigquery.Schema { return r.RowIterator.Schema }
201 | func (r rowIterator) TotalRows() uint64 { return r.RowIterator.TotalRows }
202 | func (r rowIterator) Next(dst interface{}) error { return r.RowIterator.Next(dst) }
203 |
204 | func (t table) ProjectID() string { return t.Table.ProjectID }
205 | func (t table) DatasetID() string { return t.Table.DatasetID }
206 | func (t table) TableID() string { return t.Table.TableID }
207 | func (t table) FullyQualifiedName() string { return t.Table.FullyQualifiedName() }
208 | func (t table) Delete(ctx context.Context) error { return t.Table.Delete(ctx) }
209 | func (t table) Uploader() Uploader { return uploader{t.Table.Uploader()} }
210 | func (t table) Read(ctx context.Context) RowIterator { return rowIterator{t.Table.Read(ctx)} }
211 |
212 | func (t table) Create(ctx context.Context, tm *bigquery.TableMetadata) error {
213 | return t.Table.Create(ctx, tm)
214 | }
215 | func (t table) Metadata(ctx context.Context) (*bigquery.TableMetadata, error) {
216 | return t.Table.Metadata(ctx)
217 | }
218 |
219 | func (t table) Update(ctx context.Context, tm bigquery.TableMetadataToUpdate, etag string) (*bigquery.TableMetadata, error) {
220 | return t.Table.Update(ctx, tm, etag)
221 | }
222 |
223 | func (t table) CopierFrom(ts ...Table) Copier {
224 | var bts []*bigquery.Table
225 | for _, tb := range ts {
226 | bts = append(bts, tb.(table).Table)
227 | }
228 | c := t.Table.CopierFrom(bts...)
229 | return copier{c}
230 | }
231 |
232 | func (t table) ExtractorTo(dst *bigquery.GCSReference) Extractor {
233 | return extractor{t.Table.ExtractorTo(dst)}
234 | }
235 |
236 | func (t table) LoaderFrom(s bigquery.LoadSource) Loader {
237 | return loader{t.Table.LoaderFrom(s)}
238 | }
239 |
240 | func (ti tableIterator) Next() (Table, error) {
241 | t, err := ti.TableIterator.Next()
242 | if err != nil {
243 | return nil, err
244 | }
245 | return table{t}, nil
246 | }
247 |
248 | func (u uploader) SetSkipInvalidRows(b bool) { u.Uploader.SkipInvalidRows = b }
249 | func (u uploader) SetIgnoreUnknownValues(b bool) { u.Uploader.IgnoreUnknownValues = b }
250 | func (u uploader) SetTableTemplateSuffix(s string) { u.Uploader.TableTemplateSuffix = s }
251 | func (u uploader) Put(ctx context.Context, i interface{}) error { return u.Uploader.Put(ctx, i) }
252 |
253 | func adaptJob(j *bigquery.Job, err error) (Job, error) {
254 | if err != nil {
255 | return nil, err
256 | }
257 | return job{j}, nil
258 | }
259 |
260 | func (m *DatasetMetadata) toBQ() *bigquery.DatasetMetadata {
261 | m.DatasetMetadata.Access = accessEntriesToBQ(m.Access)
262 | return &m.DatasetMetadata
263 | }
264 |
265 | func datasetMetadataFromBQ(m *bigquery.DatasetMetadata) *DatasetMetadata {
266 | if m == nil {
267 | return nil
268 | }
269 | return &DatasetMetadata{
270 | DatasetMetadata: *m,
271 | Access: accessEntriesFromBQ(m.Access),
272 | }
273 | }
274 |
275 | func (u DatasetMetadataToUpdate) toBQ() bigquery.DatasetMetadataToUpdate {
276 | u.DatasetMetadataToUpdate.Access = accessEntriesToBQ(u.Access)
277 | return u.DatasetMetadataToUpdate
278 | }
279 |
280 | func (e *AccessEntry) toBQ() *bigquery.AccessEntry {
281 | if e.View != nil {
282 | e.AccessEntry.View = e.View.(table).Table
283 | }
284 | return &e.AccessEntry
285 | }
286 |
287 | func accessEntryFromBQ(e *bigquery.AccessEntry) *AccessEntry {
288 | if e == nil {
289 | return nil
290 | }
291 | r := &AccessEntry{AccessEntry: *e}
292 | if e.View != nil {
293 | r.View = table{e.View}
294 | }
295 | return r
296 | }
297 |
298 | func accessEntriesToBQ(a []*AccessEntry) []*bigquery.AccessEntry {
299 | var r []*bigquery.AccessEntry
300 | for _, e := range a {
301 | r = append(r, e.toBQ())
302 | }
303 | return r
304 | }
305 |
306 | func accessEntriesFromBQ(a []*bigquery.AccessEntry) []*AccessEntry {
307 | var r []*AccessEntry
308 | for _, e := range a {
309 | r = append(r, accessEntryFromBQ(e))
310 | }
311 | return r
312 | }
313 |
--------------------------------------------------------------------------------
/bigquery/bqiface/bqiface_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package bqiface
16 |
17 | import (
18 | "context"
19 | "fmt"
20 | "os"
21 | "strings"
22 | "testing"
23 | "time"
24 |
25 | "cloud.google.com/go/bigquery"
26 | "google.golang.org/api/iterator"
27 | )
28 |
29 | func TestIntegration(t *testing.T) {
30 | if testing.Short() {
31 | t.Skip("integration tests skipped in short mode")
32 | }
33 | projectID := os.Getenv("BQIFACE_PROJECT")
34 | if projectID == "" {
35 | t.Skip("missing BQIFACE_PROJECT environment variable")
36 | }
37 |
38 | ctx := context.Background()
39 | c, err := bigquery.NewClient(ctx, projectID)
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 | client := AdaptClient(c)
44 | defer client.Close()
45 |
46 | ds := client.Dataset(fmt.Sprintf("bqiface_%d", time.Now().Unix()))
47 | var wantMD DatasetMetadata
48 | wantMD.DefaultTableExpiration = time.Hour
49 | var ae AccessEntry
50 | ae.Role = bigquery.OwnerRole
51 | ae.EntityType = bigquery.SpecialGroupEntity
52 | ae.Entity = "projectOwners"
53 | wantMD.Access = []*AccessEntry{&ae}
54 | err = ds.Create(ctx, &wantMD)
55 | if err != nil {
56 | t.Fatal(err)
57 | }
58 | defer func() {
59 | if err := ds.Delete(ctx); err != nil {
60 | t.Fatal(err)
61 | }
62 | }()
63 |
64 | gotMD, err := ds.Metadata(ctx)
65 | if err != nil {
66 | t.Fatal(err)
67 | }
68 | if got, want := gotMD.DefaultTableExpiration, wantMD.DefaultTableExpiration; got != want {
69 | t.Errorf("DefaultTableExpiration: got %s, want %s", got, want)
70 | }
71 | if got, want := len(gotMD.Access), 1; got != want {
72 | t.Fatalf("got %d access entries, want %d", got, want)
73 | }
74 | if got, want := *gotMD.Access[0], ae; got != want {
75 | t.Errorf("got %+v, want %+v", got, want)
76 | }
77 |
78 | table := ds.Table("t")
79 | schema := bigquery.Schema{
80 | {Name: "name", Type: bigquery.StringFieldType},
81 | {Name: "score", Type: bigquery.IntegerFieldType},
82 | }
83 | if err := table.Create(ctx, &bigquery.TableMetadata{Schema: schema}); err != nil {
84 | t.Fatal(err)
85 | }
86 | defer func() {
87 | if err := table.Delete(ctx); err != nil {
88 | t.Fatal(err)
89 | }
90 | }()
91 |
92 | upl := table.Uploader()
93 | var saverRows []*bigquery.ValuesSaver
94 |
95 | for i, name := range []string{"a", "b", "c"} {
96 | saverRows = append(saverRows, &bigquery.ValuesSaver{
97 | Schema: schema,
98 | InsertID: name,
99 | Row: []bigquery.Value{name, i},
100 | })
101 | }
102 | if err := upl.Put(ctx, saverRows); err != nil {
103 | t.Fatal(putError(err))
104 | }
105 | count := 0
106 | for {
107 | it := table.Read(ctx)
108 | count, err = countRows(it)
109 | if err != nil {
110 | t.Fatal(err)
111 | }
112 | if count > 0 {
113 | break
114 | }
115 | // Wait for rows to appear; it may take a few seconds.
116 | time.Sleep(1 * time.Second)
117 | }
118 | if got, want := count, len(saverRows); got != want {
119 | t.Errorf("got %d rows, want %d", got, want)
120 | }
121 |
122 | q := client.Query(fmt.Sprintf("SELECT * FROM %s.%s", table.DatasetID(), table.TableID()))
123 | it, err := q.Read(ctx)
124 | if err != nil {
125 | t.Fatal(err)
126 | }
127 | count, err = countRows(it)
128 | if err != nil {
129 | t.Fatal(err)
130 | }
131 | if got, want := count, len(saverRows); got != want {
132 | t.Errorf("got %d rows, want %d", got, want)
133 | }
134 | }
135 |
136 | func countRows(it RowIterator) (int, error) {
137 | n := 0
138 | for {
139 | var v []bigquery.Value
140 | err := it.Next(&v)
141 | if err == iterator.Done {
142 | return n, nil
143 | }
144 | if err != nil {
145 | return 0, err
146 | }
147 | n++
148 | }
149 | }
150 |
151 | func putError(err error) string {
152 | pme, ok := err.(bigquery.PutMultiError)
153 | if !ok {
154 | return err.Error()
155 | }
156 | var msgs []string
157 | for _, err := range pme {
158 | msgs = append(msgs, err.Error())
159 | }
160 | return strings.Join(msgs, "\n")
161 | }
162 |
--------------------------------------------------------------------------------
/bigquery/bqiface/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package bqiface provides a set of interfaces for the types in
16 | // cloud.google.com/go/bigquery. These can be used to create mocks or other
17 | // test doubles. The package also provides adapters to enable the types of the
18 | // bigquery package to implement these interfaces.
19 | //
20 | // We do not recommend using mocks for most testing. Please read
21 | // https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html.
22 | //
23 | // Note: This package is in alpha. Some backwards-incompatible changes may occur.
24 | //
25 | // You must embed these interfaces to implement them:
26 | //
27 | // type ClientMock struct {
28 | // bqiface.Client
29 | // ...
30 | // }
31 | //
32 | // This ensures that your implementations will not break when methods are added to the interfaces.
33 | package bqiface
34 |
--------------------------------------------------------------------------------
/bigquery/bqiface/examples_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package bqiface_test
16 |
17 | import (
18 | "context"
19 |
20 | "cloud.google.com/go/bigquery"
21 | "github.com/googleapis/google-cloud-go-testing/bigquery/bqiface"
22 | )
23 |
24 | func Example_AdaptClient() {
25 | ctx := context.Background()
26 | c, err := bigquery.NewClient(ctx, "my-project")
27 | if err != nil {
28 | // TODO: Handle error.
29 | }
30 | client := bqiface.AdaptClient(c)
31 | defer client.Close()
32 | ds := client.Dataset("my_dataset")
33 | md, err := ds.Metadata(ctx)
34 | if err != nil {
35 | // TODO: Handle error.
36 | }
37 | _ = md // TODO: use md.
38 | }
39 |
--------------------------------------------------------------------------------
/bigquery/bqiface/interfaces.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package bqiface
16 |
17 | import (
18 | "context"
19 |
20 | "cloud.google.com/go/bigquery"
21 | "google.golang.org/api/iterator"
22 | )
23 |
24 | type Client interface {
25 | Location() string
26 | SetLocation(string)
27 | Close() error
28 | Dataset(string) Dataset
29 | DatasetInProject(string, string) Dataset
30 | Datasets(context.Context) DatasetIterator
31 | DatasetsInProject(context.Context, string) DatasetIterator
32 | Query(string) Query
33 | JobFromID(context.Context, string) (Job, error)
34 | JobFromIDLocation(context.Context, string, string) (Job, error)
35 | Jobs(context.Context) JobIterator
36 |
37 | embedToIncludeNewMethods()
38 | }
39 |
40 | type Copier interface {
41 | JobIDConfig() *bigquery.JobIDConfig
42 | SetCopyConfig(CopyConfig)
43 | Run(context.Context) (Job, error)
44 |
45 | embedToIncludeNewMethods()
46 | }
47 |
48 | type Dataset interface {
49 | ProjectID() string
50 | DatasetID() string
51 | Create(context.Context, *DatasetMetadata) error
52 | Delete(context.Context) error
53 | DeleteWithContents(context.Context) error
54 | Metadata(context.Context) (*DatasetMetadata, error)
55 | Update(context.Context, DatasetMetadataToUpdate, string) (*DatasetMetadata, error)
56 | Table(string) Table
57 | Tables(context.Context) TableIterator
58 |
59 | embedToIncludeNewMethods()
60 | }
61 |
62 | type DatasetIterator interface {
63 | SetListHidden(bool)
64 | SetFilter(string)
65 | SetProjectID(string)
66 | Next() (Dataset, error)
67 | PageInfo() *iterator.PageInfo
68 |
69 | embedToIncludeNewMethods()
70 | }
71 |
72 | type Extractor interface {
73 | JobIDConfig() *bigquery.JobIDConfig
74 | SetExtractConfig(ExtractConfig)
75 | Run(context.Context) (Job, error)
76 |
77 | embedToIncludeNewMethods()
78 | }
79 |
80 | type Loader interface {
81 | JobIDConfig() *bigquery.JobIDConfig
82 | SetLoadConfig(LoadConfig)
83 | Run(context.Context) (Job, error)
84 |
85 | embedToIncludeNewMethods()
86 | }
87 |
88 | type Job interface {
89 | ID() string
90 | Location() string
91 | Config() (bigquery.JobConfig, error)
92 | Status(context.Context) (*bigquery.JobStatus, error)
93 | LastStatus() *bigquery.JobStatus
94 | Cancel(context.Context) error
95 | Wait(context.Context) (*bigquery.JobStatus, error)
96 | Read(context.Context) (RowIterator, error)
97 |
98 | embedToIncludeNewMethods()
99 | }
100 |
101 | type JobIterator interface {
102 | SetProjectID(string)
103 | SetAllUsers(bool)
104 | SetState(bigquery.State)
105 | Next() (Job, error)
106 | PageInfo() *iterator.PageInfo
107 |
108 | embedToIncludeNewMethods()
109 | }
110 |
111 | type Query interface {
112 | JobIDConfig() *bigquery.JobIDConfig
113 | SetQueryConfig(QueryConfig)
114 | Run(context.Context) (Job, error)
115 | Read(context.Context) (RowIterator, error)
116 |
117 | embedToIncludeNewMethods()
118 | }
119 |
120 | type RowIterator interface {
121 | SetStartIndex(uint64)
122 | Schema() bigquery.Schema
123 | TotalRows() uint64
124 | Next(interface{}) error
125 | PageInfo() *iterator.PageInfo
126 |
127 | embedToIncludeNewMethods()
128 | }
129 |
130 | type Table interface {
131 | CopierFrom(...Table) Copier
132 | Create(context.Context, *bigquery.TableMetadata) error
133 | DatasetID() string
134 | Delete(context.Context) error
135 | ExtractorTo(dst *bigquery.GCSReference) Extractor
136 | FullyQualifiedName() string
137 | LoaderFrom(bigquery.LoadSource) Loader
138 | Metadata(context.Context) (*bigquery.TableMetadata, error)
139 | ProjectID() string
140 | Read(ctx context.Context) RowIterator
141 | TableID() string
142 | Update(context.Context, bigquery.TableMetadataToUpdate, string) (*bigquery.TableMetadata, error)
143 | Uploader() Uploader
144 |
145 | embedToIncludeNewMethods()
146 | }
147 |
148 | type TableIterator interface {
149 | Next() (Table, error)
150 | PageInfo() *iterator.PageInfo
151 |
152 | embedToIncludeNewMethods()
153 | }
154 |
155 | type Uploader interface {
156 | SetSkipInvalidRows(bool)
157 | SetIgnoreUnknownValues(bool)
158 | SetTableTemplateSuffix(string)
159 | Put(context.Context, interface{}) error
160 |
161 | embedToIncludeNewMethods()
162 | }
163 |
--------------------------------------------------------------------------------
/bigquery/bqiface/structs.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package bqiface
16 |
17 | import (
18 | "cloud.google.com/go/bigquery"
19 | )
20 |
21 | type AccessEntry struct {
22 | bigquery.AccessEntry
23 | View Table // shadows bigquery.AccessEntry's field
24 | }
25 |
26 | type CopyConfig struct {
27 | bigquery.CopyConfig
28 | Srcs []Table // shadows bigquery.CopyConfig's field
29 | Dst Table // shadows bigquery.CopyConfig's field
30 | }
31 |
32 | type DatasetMetadata struct {
33 | bigquery.DatasetMetadata
34 | Access []*AccessEntry // shadows bigquery.DatasetMetadata's field
35 | }
36 |
37 | type DatasetMetadataToUpdate struct {
38 | bigquery.DatasetMetadataToUpdate
39 | Access []*AccessEntry // shadows bigquery.DatasetMetadataToUpdate's field
40 | }
41 |
42 | type ExtractConfig struct {
43 | bigquery.ExtractConfig
44 | Src Table // shadows bigquery.ExtractConfig's field
45 | }
46 |
47 | type LoadConfig struct {
48 | bigquery.LoadConfig
49 | Dst Table // shadows bigquery.LoadConfig's field
50 | }
51 |
52 | type QueryConfig struct {
53 | bigquery.QueryConfig
54 | Dst Table // shaodws bigquery.QueryConfig's field
55 | }
56 |
--------------------------------------------------------------------------------
/datastore/dsiface/README.md:
--------------------------------------------------------------------------------
1 | Datastore
2 | =========
3 |
4 | ## Run Tests
5 |
6 | ### Run unit tests
7 |
8 | The unit tests run locally against an example fake and do not require any
9 | configuration.
10 |
11 | ```
12 | go test --short
13 | ```
14 |
15 | ### Run all tests
16 |
17 | The integration tests will reach out and store, read and then delete a value
18 | from a datastore. You have to provide the `DATASTORE_PROJECT_ID` environment
19 | variable and the client has to be able to [find credentials][find-creds].
20 |
21 | For example:
22 |
23 | ```
24 | DATASTORE_PROJECT_ID= GOOGLE_APPLICATION_CREDENTIALS= go test
25 | ```
26 |
27 | [find-creds]: https://godoc.org/cloud.google.com/go#hdr-Authentication_and_Authorization
28 |
--------------------------------------------------------------------------------
/datastore/dsiface/adapters.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package dsiface
16 |
17 | import (
18 | "context"
19 |
20 | "cloud.google.com/go/datastore"
21 | )
22 |
23 | // AdaptClient adapts a datastore.Client so that it satisfies the Client
24 | // interface.
25 | func AdaptClient(c *datastore.Client) Client {
26 | return client{c}
27 | }
28 |
29 | type (
30 | client struct{ *datastore.Client }
31 | transaction struct{ *datastore.Transaction }
32 | iterator struct{ *datastore.Iterator }
33 | commit struct{ *datastore.Commit }
34 | )
35 |
36 | func (client) embedToIncludeNewMethods() {}
37 | func (transaction) embedToIncludeNewMethods() {}
38 | func (iterator) embedToIncludeNewMethods() {}
39 | func (commit) embedToIncludeNewMethods() {}
40 |
41 | func (c client) Close() error {
42 | return c.Client.Close()
43 | }
44 |
45 | func (c client) AllocateIDs(ctx context.Context, keys []*datastore.Key) ([]*datastore.Key, error) {
46 | return c.Client.AllocateIDs(ctx, keys)
47 | }
48 |
49 | func (c client) Count(ctx context.Context, q *datastore.Query) (int, error) {
50 | return c.Client.Count(ctx, q)
51 | }
52 |
53 | func (c client) Delete(ctx context.Context, key *datastore.Key) error {
54 | return c.Client.Delete(ctx, key)
55 | }
56 |
57 | func (c client) DeleteMulti(ctx context.Context, keys []*datastore.Key) error {
58 | return c.Client.DeleteMulti(ctx, keys)
59 | }
60 |
61 | func (c client) Get(ctx context.Context, key *datastore.Key, dst interface{}) error {
62 | return c.Client.Get(ctx, key, dst)
63 | }
64 |
65 | func (c client) GetAll(ctx context.Context, q *datastore.Query, dst interface{}) ([]*datastore.Key, error) {
66 | return c.Client.GetAll(ctx, q, dst)
67 | }
68 |
69 | func (c client) GetMulti(ctx context.Context, keys []*datastore.Key, dst interface{}) error {
70 | return c.Client.GetMulti(ctx, keys, dst)
71 | }
72 |
73 | func (c client) Mutate(ctx context.Context, muts ...*datastore.Mutation) ([]*datastore.Key, error) {
74 | return c.Client.Mutate(ctx, muts...)
75 | }
76 |
77 | func (c client) NewTransaction(ctx context.Context, opts ...datastore.TransactionOption) (Transaction, error) {
78 | t, err := c.Client.NewTransaction(ctx, opts...)
79 | return transaction{t}, err
80 | }
81 |
82 | func (c client) Put(ctx context.Context, key *datastore.Key, src interface{}) (*datastore.Key, error) {
83 | return c.Client.Put(ctx, key, src)
84 | }
85 |
86 | func (c client) PutMulti(ctx context.Context, keys []*datastore.Key, src interface{}) ([]*datastore.Key, error) {
87 | return c.Client.PutMulti(ctx, keys, src)
88 | }
89 |
90 | func (c client) Run(ctx context.Context, q *datastore.Query) Iterator {
91 | return iterator{c.Client.Run(ctx, q)}
92 | }
93 |
94 | func (c client) RunInTransaction(ctx context.Context, f func(tx Transaction) error, opts ...datastore.TransactionOption) (Commit, error) {
95 | cmt, err := c.Client.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
96 | return f(transaction{tx})
97 | }, opts...)
98 | return commit{cmt}, err
99 | }
100 |
101 | func (t transaction) Commit() (Commit, error) {
102 | c, err := t.Transaction.Commit()
103 | return commit{c}, err
104 | }
105 |
106 | func (t transaction) Delete(key *datastore.Key) error {
107 | return t.Transaction.Delete(key)
108 | }
109 |
110 | func (t transaction) DeleteMulti(keys []*datastore.Key) error {
111 | return t.Transaction.DeleteMulti(keys)
112 | }
113 |
114 | func (t transaction) Get(key *datastore.Key, dst interface{}) error {
115 | return t.Transaction.Get(key, dst)
116 | }
117 |
118 | func (t transaction) GetMulti(keys []*datastore.Key, dst interface{}) error {
119 | return t.Transaction.GetMulti(keys, dst)
120 | }
121 |
122 | func (t transaction) Mutate(muts ...*datastore.Mutation) ([]*datastore.PendingKey, error) {
123 | return t.Transaction.Mutate(muts...)
124 | }
125 |
126 | func (t transaction) Put(key *datastore.Key, src interface{}) (*datastore.PendingKey, error) {
127 | return t.Transaction.Put(key, src)
128 | }
129 |
130 | func (t transaction) PutMulti(keys []*datastore.Key, src interface{}) ([]*datastore.PendingKey, error) {
131 | return t.Transaction.PutMulti(keys, src)
132 | }
133 |
134 | func (t transaction) Rollback() error {
135 | return t.Transaction.Rollback()
136 | }
137 |
138 | func (c commit) Key(p *datastore.PendingKey) *datastore.Key {
139 | return c.Commit.Key(p)
140 | }
141 |
--------------------------------------------------------------------------------
/datastore/dsiface/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package dsiface provides a set of interfaces for the types in
16 | // cloud.google.com/go/datastore. These can be used to create mocks or other
17 | // test doubles. The package also provides adapters to enable the types of the
18 | // datastore package to implement these interfaces.
19 | //
20 | // We do not recommend using mocks for most testing. Please read
21 | // https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html.
22 | //
23 | // Note: This package is in alpha. Some backwards-incompatible changes may occur.
24 | //
25 | // You must embed these interfaces to implement them:
26 | //
27 | // type ClientMock struct {
28 | // dsiface.Client
29 | // ...
30 | // }
31 | //
32 | // This ensures that your implementations will not break when methods are added
33 | // to the interfaces.
34 | package dsiface
35 |
--------------------------------------------------------------------------------
/datastore/dsiface/dsiface_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package dsiface
16 |
17 | import (
18 | "context"
19 | "fmt"
20 | "math/rand"
21 | "os"
22 | "testing"
23 | "time"
24 |
25 | "cloud.google.com/go/datastore"
26 | )
27 |
28 | func TestIntegration(t *testing.T) {
29 | if testing.Short() {
30 | t.Skip("integration tests skipped in short mode")
31 | }
32 |
33 | projID := os.Getenv("DATASTORE_PROJECT_ID")
34 | if projID == "" {
35 | t.Skip("missing DATASTORE_PROJECT_ID environment variable")
36 | }
37 |
38 | kind := fmt.Sprintf("dsiface_test_%d", time.Now().UnixNano())
39 | ctx := context.Background()
40 | c, err := datastore.NewClient(ctx, projID)
41 | if err != nil {
42 | t.Fatal(err)
43 | }
44 | client := AdaptClient(c)
45 | defer client.Close()
46 | basicTests(t, kind, client)
47 | }
48 |
49 | type sourceData struct {
50 | Payload string
51 | }
52 |
53 | func basicTests(t *testing.T, kindName string, client Client) {
54 | ctx := context.Background()
55 |
56 | want := "test-payload"
57 | src := sourceData{Payload: want}
58 | key, err := client.Put(ctx, &datastore.Key{Kind: kindName}, &src)
59 | if err != nil {
60 | t.Fatal(err)
61 | }
62 |
63 | var dst sourceData
64 | if err := client.Get(ctx, key, &dst); err != nil {
65 | t.Fatal(err)
66 | }
67 |
68 | if dst.Payload != want {
69 | t.Fatalf(`expected %q to equal %s`, dst.Payload, want)
70 | }
71 |
72 | if err := client.Delete(ctx, key); err != nil {
73 | t.Fatal(err)
74 | }
75 |
76 | if err := client.Get(ctx, key, &dst); err != datastore.ErrNoSuchEntity {
77 | t.Fatalf("expected ErrNoSuchEntity error: %v", err)
78 | }
79 | }
80 |
81 | // This test demonstrates how to use this package to create a simple fake for
82 | // the datastore client.
83 | func TestFake(t *testing.T) {
84 | client := newFakeClient()
85 | basicTests(t, "test-kind", client)
86 | }
87 |
88 | type fakeClient struct {
89 | Client
90 | m map[string]interface{}
91 | }
92 |
93 | func newFakeClient() Client {
94 | return &fakeClient{
95 | m: make(map[string]interface{}),
96 | }
97 | }
98 |
99 | func (c *fakeClient) Put(ctx context.Context, key *datastore.Key, src interface{}) (*datastore.Key, error) {
100 | if key.Name == "" && key.ID == 0 {
101 | key.ID = rand.Int63()
102 | }
103 |
104 | c.m[key.String()] = *(src.(*sourceData))
105 | return key, nil
106 | }
107 |
108 | func (c *fakeClient) Get(ctx context.Context, key *datastore.Key, dst interface{}) error {
109 | val, ok := c.m[key.String()]
110 | if !ok {
111 | return datastore.ErrNoSuchEntity
112 | }
113 |
114 | sd := dst.(*sourceData)
115 | sv := val.(sourceData)
116 | *sd = sv
117 | return nil
118 | }
119 |
120 | func (c *fakeClient) Delete(ctx context.Context, key *datastore.Key) error {
121 | delete(c.m, key.String())
122 | return nil
123 | }
124 |
--------------------------------------------------------------------------------
/datastore/dsiface/examples_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package dsiface_test
16 |
17 | import (
18 | "context"
19 |
20 | "cloud.google.com/go/datastore"
21 | "github.com/googleapis/google-cloud-go-testing/datastore/dsiface"
22 | )
23 |
24 | func Example_AdaptClient() {
25 | ctx := context.Background()
26 | c, err := datastore.NewClient(ctx, "")
27 | if err != nil {
28 | // TODO: Handle error.
29 | }
30 | client := dsiface.AdaptClient(c)
31 | // TODO: Use client.
32 | _ = client
33 | }
34 |
--------------------------------------------------------------------------------
/datastore/dsiface/interfaces.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package dsiface
16 |
17 | import (
18 | "context"
19 |
20 | "cloud.google.com/go/datastore"
21 | )
22 |
23 | // Client is the interface that wraps a datastore.Client.
24 | type Client interface {
25 | Close() error
26 | AllocateIDs(ctx context.Context, keys []*datastore.Key) ([]*datastore.Key, error)
27 | Count(ctx context.Context, q *datastore.Query) (n int, err error)
28 | Delete(ctx context.Context, key *datastore.Key) error
29 | DeleteMulti(ctx context.Context, keys []*datastore.Key) (err error)
30 | Get(ctx context.Context, key *datastore.Key, dst interface{}) (err error)
31 | GetAll(ctx context.Context, q *datastore.Query, dst interface{}) (keys []*datastore.Key, err error)
32 | GetMulti(ctx context.Context, keys []*datastore.Key, dst interface{}) (err error)
33 | Mutate(ctx context.Context, muts ...*datastore.Mutation) (ret []*datastore.Key, err error)
34 | NewTransaction(ctx context.Context, opts ...datastore.TransactionOption) (t Transaction, err error)
35 | Put(ctx context.Context, key *datastore.Key, src interface{}) (*datastore.Key, error)
36 | PutMulti(ctx context.Context, keys []*datastore.Key, src interface{}) (ret []*datastore.Key, err error)
37 | Run(ctx context.Context, q *datastore.Query) Iterator
38 | RunInTransaction(ctx context.Context, f func(tx Transaction) error, opts ...datastore.TransactionOption) (cmt Commit, err error)
39 |
40 | embedToIncludeNewMethods()
41 | }
42 |
43 | // Transaction is the interface that wraps a datastore.Transaction.
44 | type Transaction interface {
45 | Commit() (c Commit, err error)
46 | Delete(key *datastore.Key) error
47 | DeleteMulti(keys []*datastore.Key) (err error)
48 | Get(key *datastore.Key, dst interface{}) (err error)
49 | GetMulti(keys []*datastore.Key, dst interface{}) (err error)
50 | Mutate(muts ...*datastore.Mutation) ([]*datastore.PendingKey, error)
51 | Put(key *datastore.Key, src interface{}) (*datastore.PendingKey, error)
52 | PutMulti(keys []*datastore.Key, src interface{}) (ret []*datastore.PendingKey, err error)
53 | Rollback() (err error)
54 |
55 | embedToIncludeNewMethods()
56 | }
57 |
58 | // Iterator is the interface that wraps a datastore.Iterator.
59 | type Iterator interface {
60 | Cursor() (c datastore.Cursor, err error)
61 | Next(dst interface{}) (k *datastore.Key, err error)
62 |
63 | embedToIncludeNewMethods()
64 | }
65 |
66 | // Commit is the interface that wraps a datastore.Commit.
67 | type Commit interface {
68 | Key(p *datastore.PendingKey) *datastore.Key
69 |
70 | embedToIncludeNewMethods()
71 | }
72 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Google LLC
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | /*
18 | These packages contain code that can help you test against the GCP Client
19 | Libraries for Go (https://github.com/GoogleCloudPlatform/google-cloud-go).
20 |
21 | We do not recommend using mocks for most testing. Please read
22 | https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html.
23 |
24 | Note: These packages are in alpha. Some backwards-incompatible changes may
25 | occur.
26 |
27 |
28 | Embedding Interfaces
29 |
30 | All interfaces in this package include an embedToIncludeNewMethods method. This
31 | is intentionally unexported so that any implementor of the interface must
32 | embed the interface in their implementation. Embedding the interface in an
33 | implementation has the effect that any future methods added to the interface
34 | will not cause compile-time errors (the implementation does not implement
35 | the newly-added method), since embedded interfaces provide a default method for
36 | unimplemented methods.
37 |
38 | See Example (RecordBuckets) for an example of how to implement interfaces
39 | (including embedding the interface).
40 | */
41 | package googlecloudgotesting
42 |
--------------------------------------------------------------------------------
/examples_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package googlecloudgotesting
16 |
17 | import (
18 | "context"
19 | "fmt"
20 |
21 | "cloud.google.com/go/storage"
22 | "github.com/googleapis/google-cloud-go-testing/storage/stiface"
23 | )
24 |
25 | type RecordingClient struct {
26 | stiface.Client
27 | bucketCalls int
28 | }
29 |
30 | func (rc *RecordingClient) Bucket(name string) stiface.BucketHandle {
31 | rc.bucketCalls++
32 | return rc.Client.Bucket(name)
33 | }
34 |
35 | // We do not need to implement methods that we don't want to record - by default
36 | // the embedded type will be used.
37 |
38 | func Example_recordBuckets() {
39 | // This example demonstrates building a simple mock that counts the number
40 | // of Bucket calls before calling the real client and returning its output.
41 |
42 | ctx := context.Background()
43 | c, err := storage.NewClient(ctx)
44 | if err != nil {
45 | // TODO: Handle error.
46 | }
47 | client := stiface.AdaptClient(c)
48 | recordingClient := RecordingClient{client, 0}
49 |
50 | recordingClient.Bucket("my-bucket-1")
51 | recordingClient.Bucket("my-bucket-2")
52 | recordingClient.Bucket("my-bucket-3")
53 |
54 | fmt.Println(recordingClient.bucketCalls)
55 | // Output: 3
56 | }
57 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/googleapis/google-cloud-go-testing
2 |
3 | go 1.11
4 |
5 | require (
6 | cloud.google.com/go v0.44.3
7 | cloud.google.com/go/bigquery v1.0.1
8 | cloud.google.com/go/datastore v1.0.0
9 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422
10 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0
11 | google.golang.org/api v0.9.0
12 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a
13 | )
14 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
2 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
4 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
5 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
6 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
7 | cloud.google.com/go v0.44.3 h1:0sMegbmn/8uTwpNkB0q9cLEpZ2W5a6kl+wtBQgPWBJQ=
8 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
9 | cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU=
10 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
11 | cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM=
12 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
13 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
15 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
16 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
17 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
18 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
19 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
20 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
21 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
22 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
23 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
24 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
25 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
26 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
27 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
28 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
29 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
30 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
31 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
32 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
33 | github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
34 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
35 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
36 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
37 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
38 | github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
39 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
40 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
41 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
42 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
43 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc=
44 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
45 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
46 | go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
47 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
48 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
49 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
50 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
51 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522 h1:OeRHuibLsmZkFj773W4LcfAGsSxJgfPONhr8cmO+eLA=
52 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
53 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
54 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc=
55 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
56 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
57 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f h1:hX65Cu3JDlGH3uEdK7I99Ii+9kjD6mvnnpfLdEAH0x4=
58 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
59 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
60 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
61 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
62 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
63 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
64 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
65 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
66 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
67 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
68 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
69 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
70 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
71 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
72 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
73 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
74 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
75 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
76 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
77 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
78 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
79 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
80 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
81 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
82 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
83 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
84 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
85 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
86 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
87 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
88 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
89 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
90 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
91 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
92 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
93 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
94 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
95 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
96 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
97 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
98 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
99 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
100 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
101 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
102 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
103 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
104 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
105 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
106 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
107 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
108 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
109 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
110 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
111 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
112 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE=
113 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
114 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
115 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
116 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
117 | google.golang.org/api v0.9.0 h1:jbyannxz0XFD3zdjgrSUsaJbgpH4eTrkdhRChkHPfO8=
118 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
119 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
120 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
121 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
122 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
123 | google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
124 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
125 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
126 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
127 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
128 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
129 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
130 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 h1:iKtrH9Y8mcbADOP0YFaEMth7OfuHY9xHOwNj4znpM1A=
131 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
132 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
133 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
134 | google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
135 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
136 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
137 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
138 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA=
139 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
140 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
141 |
--------------------------------------------------------------------------------
/internal/kokoro/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # TODO(deklerk) Add integration tests when it's secure to do so. b/64723143
4 |
5 | # Fail on any error
6 | set -eo pipefail
7 |
8 | # Display commands being run
9 | set -x
10 |
11 | # cd to project dir on Kokoro instance
12 | cd github/google-cloud-go-testing
13 |
14 | go version
15 |
16 | # Set $GOPATH
17 | export GOPATH="$HOME/go"
18 | export GCGT_HOME=$GOPATH/src/github.com/googleapis/google-cloud-go-testing
19 | export PATH="$GOPATH/bin:$PATH"
20 | export GO111MODULE=on
21 | mkdir -p $GCGT_HOME
22 |
23 | # Move code into $GOPATH and get dependencies
24 | git clone . $GCGT_HOME
25 | cd $GCGT_HOME
26 |
27 | try3() { eval "$*" || eval "$*" || eval "$*"; }
28 |
29 | # All packages, including +build tools, are fetched.
30 | try3 go mod download
31 | ./internal/kokoro/vet.sh
32 |
33 | # Run tests and tee output to log file, to be pushed to GCS as artifact.
34 | go test -race -v ./... 2>&1 | tee $KOKORO_ARTIFACTS_DIR/$KOKORO_GERRIT_CHANGE_NUMBER.txt
35 |
--------------------------------------------------------------------------------
/internal/kokoro/trampoline.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright 2018 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | set -eo pipefail
16 | # Always run the cleanup script, regardless of the success of bouncing into
17 | # the container.
18 | function cleanup() {
19 | chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh
20 | ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh
21 | echo "cleanup";
22 | }
23 | trap cleanup EXIT
24 | python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py"
25 |
--------------------------------------------------------------------------------
/internal/kokoro/vet.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Display commands being run
4 | set -x
5 |
6 | # Only run the linter on go1.12, since it needs type aliases (and we only care
7 | # about its output once).
8 | if [[ `go version` != *"go1.12"* ]]; then
9 | exit 0
10 | fi
11 |
12 | go install \
13 | golang.org/x/lint/golint \
14 | golang.org/x/tools/cmd/goimports \
15 | honnef.co/go/tools/cmd/staticcheck
16 |
17 | # Fail if a dependency was added without the necessary go.mod/go.sum change
18 | # being part of the commit.
19 | go mod tidy
20 | git diff go.mod | tee /dev/stderr | (! read)
21 | git diff go.sum | tee /dev/stderr | (! read)
22 |
23 | # Easier to debug CI.
24 | pwd
25 |
26 | # Look at all .go files (ignoring .pb.go files) and make sure they have a Copyright. Fail if any don't.
27 | find . -type f -name "*.go" ! -name "*.pb.go" -exec grep -L "\(Copyright [0-9]\{4,\}\)" {} \; 2>&1 | tee /dev/stderr | (! read)
28 | gofmt -s -d -l . 2>&1 | tee /dev/stderr | (! read)
29 | goimports -l . 2>&1 | tee /dev/stderr | (! read)
30 |
31 | golint ./... 2>&1 | ( \
32 | grep -v "should have comment or be unexported" | \
33 | grep -v "doc.go:17:1: package comment should be of the form" || true) | tee /dev/stderr | (! read)
34 |
35 | staticcheck -ignore '
36 | *:SA1019
37 | ' ./...
38 |
--------------------------------------------------------------------------------
/pubsub/psiface/adapters.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package psiface
16 |
17 | import (
18 | "context"
19 | "time"
20 |
21 | "cloud.google.com/go/pubsub"
22 | )
23 |
24 | // AdaptClient adapts a pubsub.Client so that it satisfies the Client
25 | // interface.
26 | func AdaptClient(c *pubsub.Client) Client {
27 | return client{c}
28 | }
29 |
30 | // AdaptMessage adapts a pubsub.Message so that it satisfies the Message
31 | // interface.
32 | func AdaptMessage(msg *pubsub.Message) Message {
33 | return message{msg}
34 | }
35 |
36 | type (
37 | client struct{ *pubsub.Client }
38 | topic struct{ *pubsub.Topic }
39 | subscription struct{ *pubsub.Subscription }
40 | message struct{ *pubsub.Message }
41 | publishResult struct{ *pubsub.PublishResult }
42 | )
43 |
44 | func (client) embedToIncludeNewMethods() {}
45 | func (topic) embedToIncludeNewMethods() {}
46 | func (subscription) embedToIncludeNewMethods() {}
47 | func (message) embedToIncludeNewMethods() {}
48 | func (publishResult) embedToIncludeNewMethods() {}
49 |
50 | func (c client) CreateTopic(ctx context.Context, topicID string) (Topic, error) {
51 | t, err := c.Client.CreateTopic(ctx, topicID)
52 | if err != nil {
53 | return nil, err
54 | }
55 | return topic{t}, nil
56 | }
57 |
58 | func (c client) Topic(id string) Topic {
59 | return topic{c.Client.Topic(id)}
60 | }
61 |
62 | func (c client) CreateSubscription(ctx context.Context, id string, cfg SubscriptionConfig) (Subscription, error) {
63 | s, err := c.Client.CreateSubscription(ctx, id, cfg.toPS())
64 | if err != nil {
65 | return nil, err
66 | }
67 | return subscription{s}, nil
68 | }
69 |
70 | func (c client) Subscription(id string) Subscription {
71 | return subscription{c.Client.Subscription(id)}
72 | }
73 |
74 | func (t topic) String() string {
75 | return t.Topic.String()
76 | }
77 |
78 | func (t topic) Publish(ctx context.Context, msg Message) PublishResult {
79 | return publishResult{t.Topic.Publish(ctx, msg.(message).Message)}
80 | }
81 |
82 | func (s subscription) Exists(ctx context.Context) (bool, error) {
83 | return s.Subscription.Exists(ctx)
84 | }
85 |
86 | func (s subscription) Receive(ctx context.Context, f func(ctx context.Context, msg Message)) error {
87 | return s.Subscription.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) {
88 | f(ctx, AdaptMessage(msg))
89 | })
90 | }
91 |
92 | func (s subscription) Delete(ctx context.Context) error {
93 | return s.Subscription.Delete(ctx)
94 | }
95 |
96 | func (m message) ID() string {
97 | return m.Message.ID
98 | }
99 |
100 | func (m message) Data() []byte {
101 | return m.Message.Data
102 | }
103 |
104 | func (m message) Attributes() map[string]string {
105 | return m.Message.Attributes
106 | }
107 |
108 | func (m message) PublishTime() time.Time {
109 | return m.Message.PublishTime
110 | }
111 |
112 | func (r publishResult) Get(ctx context.Context) (serverID string, err error) {
113 | return r.PublishResult.Get(ctx)
114 | }
115 |
116 | func (cfg SubscriptionConfig) toPS() pubsub.SubscriptionConfig {
117 | return pubsub.SubscriptionConfig{
118 | Topic: cfg.Topic.(topic).Topic,
119 | PushConfig: cfg.PushConfig,
120 | AckDeadline: cfg.AckDeadline,
121 | RetainAckedMessages: cfg.RetainAckedMessages,
122 | RetentionDuration: cfg.RetentionDuration,
123 | Labels: cfg.Labels,
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/pubsub/psiface/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package psiface provides a set of interfaces for the types in
16 | // cloud.google.com/go/pubsub. These can be used to create mocks or other test
17 | // doubles. The package also provides adapters to enable the types of the
18 | // pubsub package to implement these interfaces.
19 | //
20 | // We do not recommend using mocks for most testing. Please read
21 | // https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html.
22 | //
23 | // Note: This package is in alpha. Some backwards-incompatible changes may occur.
24 | //
25 | // You must embed these interfaces to implement them:
26 | //
27 | // type ClientMock struct {
28 | // psiface.Client
29 | // ...
30 | // }
31 | //
32 | // This ensures that your implementations will not break when methods are added
33 | // to the interfaces.
34 | package psiface
35 |
--------------------------------------------------------------------------------
/pubsub/psiface/examples_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package psiface_test
16 |
17 | import (
18 | "context"
19 |
20 | "cloud.google.com/go/pubsub"
21 | "github.com/googleapis/google-cloud-go-testing/pubsub/psiface"
22 | )
23 |
24 | func ExampleAdaptClient() {
25 | ctx := context.Background()
26 | c, err := pubsub.NewClient(ctx, "")
27 | if err != nil {
28 | // TODO: Handle error.
29 | }
30 | client := psiface.AdaptClient(c)
31 | msg := psiface.AdaptMessage(&pubsub.Message{})
32 | _, err = client.Topic("my-topic").Publish(ctx, msg).Get(ctx)
33 | if err != nil {
34 | // TODO: Handle error.
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/pubsub/psiface/interfaces.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package psiface
16 |
17 | import (
18 | "context"
19 | "time"
20 | )
21 |
22 | type Client interface {
23 | CreateTopic(ctx context.Context, topicID string) (Topic, error)
24 | Topic(id string) Topic
25 | CreateSubscription(ctx context.Context, id string, cfg SubscriptionConfig) (Subscription, error)
26 | Subscription(id string) Subscription
27 |
28 | embedToIncludeNewMethods()
29 | }
30 |
31 | type Topic interface {
32 | String() string
33 | Publish(ctx context.Context, msg Message) PublishResult
34 |
35 | embedToIncludeNewMethods()
36 | }
37 |
38 | type Subscription interface {
39 | Exists(ctx context.Context) (bool, error)
40 | Receive(ctx context.Context, f func(context.Context, Message)) error
41 | Delete(ctx context.Context) error
42 |
43 | embedToIncludeNewMethods()
44 | }
45 |
46 | type Message interface {
47 | ID() string
48 | Data() []byte
49 | Attributes() map[string]string
50 | PublishTime() time.Time
51 | Ack()
52 | Nack()
53 |
54 | embedToIncludeNewMethods()
55 | }
56 |
57 | type PublishResult interface {
58 | Get(ctx context.Context) (serverID string, err error)
59 |
60 | embedToIncludeNewMethods()
61 | }
62 |
--------------------------------------------------------------------------------
/pubsub/psiface/psiface_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package psiface
16 |
17 | import (
18 | "context"
19 | "errors"
20 | "fmt"
21 | "os"
22 | "strings"
23 | "sync"
24 | "testing"
25 | "time"
26 |
27 | "cloud.google.com/go/pubsub"
28 | )
29 |
30 | func TestIntegration(t *testing.T) {
31 | if testing.Short() {
32 | t.Skip("integration tests skipped in short mode")
33 | }
34 |
35 | msg := AdaptMessage(&pubsub.Message{Data: []byte("hello, psiface")})
36 |
37 | topicID := os.Getenv("PSIFACE_TOPIC")
38 | if topicID == "" {
39 | t.Skip("missing PSIFACE_TOPIC environment variable")
40 | }
41 | projID, topicName, err := parseTopic(topicID)
42 | if err != nil {
43 | t.Fatal(err)
44 | }
45 |
46 | subscriptionName := fmt.Sprintf("psiface_test_%d", time.Now().UnixNano())
47 |
48 | ctx := context.Background()
49 | c, err := pubsub.NewClient(ctx, projID)
50 | if err != nil {
51 | t.Fatal(err)
52 | }
53 | client := AdaptClient(c)
54 |
55 | basicTests(t, msg, topicName, subscriptionName, client)
56 | }
57 |
58 | func basicTests(t *testing.T, msg Message, topicName string, subscriptionName string, client Client) {
59 | ctx := context.Background()
60 | topic := client.Topic(topicName)
61 |
62 | sub, err := client.CreateSubscription(ctx, subscriptionName, SubscriptionConfig{Topic: topic})
63 | if err != nil {
64 | t.Fatal(err)
65 | }
66 |
67 | contents := string(msg.Data())
68 | ctx, cancel := context.WithCancel(ctx)
69 | errs := make(chan error, 50)
70 | go func() {
71 | err := sub.Receive(ctx, func(ctx context.Context, msg Message) {
72 | got, want := string(msg.Data()), contents
73 | msg.Ack()
74 | if got == want {
75 | errs <- nil
76 | cancel()
77 | }
78 | })
79 | if err != nil {
80 | errs <- err
81 | }
82 | }()
83 |
84 | _, err = topic.Publish(ctx, msg).Get(ctx)
85 | if err != nil {
86 | t.Fatal(err)
87 | }
88 | err = <-errs
89 | if err != nil {
90 | t.Fatal(err)
91 | }
92 |
93 | ctx = context.Background()
94 | err = sub.Delete(ctx)
95 | if err != nil {
96 | t.Errorf("deleting: %v", err)
97 | }
98 | }
99 |
100 | func parseTopic(topicID string) (project, topic string, err error) {
101 | segs := strings.Split(topicID, "/")
102 | if len(segs) != 4 || segs[0] != "projects" || segs[2] != "topics" {
103 | return "", "", errors.New("invalid topic id")
104 | }
105 | return segs[1], segs[3], nil
106 | }
107 |
108 | // This test demonstrates how to use this package to create a simple fake for
109 | // the pubsub client.
110 | func TestFake(t *testing.T) {
111 | ctx := context.Background()
112 | msg := newFakeMessage("my-msg", []byte("hello, psiface"), nil, time.Now())
113 | client := newFakeClient()
114 | if _, err := client.CreateTopic(ctx, "my-topic"); err != nil {
115 | t.Fatal(err)
116 | }
117 | basicTests(t, msg, "my-topic", "my-subscription", client)
118 | }
119 |
120 | type fakeClient struct {
121 | Client
122 | topics sync.Map
123 | subs sync.Map
124 | }
125 |
126 | func newFakeClient() Client {
127 | return &fakeClient{}
128 | }
129 |
130 | func (c *fakeClient) CreateTopic(_ context.Context, topicID string) (Topic, error) {
131 | if _, ok := c.topics.Load(topicID); ok {
132 | return nil, fmt.Errorf("topic %q already exists", topicID)
133 | }
134 | t := &fakeTopic{c: c, name: topicID}
135 | c.topics.Store(topicID, t)
136 | return t, nil
137 | }
138 |
139 | func (c *fakeClient) Topic(id string) Topic {
140 | t, ok := c.topics.Load(id)
141 | if !ok {
142 | return &fakeTopic{c: c, name: id}
143 | }
144 | return t.(Topic)
145 | }
146 |
147 | func (c *fakeClient) CreateSubscription(ctx context.Context, id string, cfg SubscriptionConfig) (Subscription, error) {
148 | if _, ok := c.subs.Load(id); ok {
149 | return nil, fmt.Errorf("subscription %q already exists", id)
150 | }
151 | s := &fakeSubscription{
152 | c: c,
153 | name: id,
154 | topicID: cfg.Topic.String(),
155 | msgs: make(chan Message, 50),
156 | }
157 | c.subs.Store(id, s)
158 | t := cfg.Topic.(*fakeTopic)
159 | t.subs = append(t.subs, s)
160 | return s, nil
161 | }
162 |
163 | func (c *fakeClient) Subscription(id string) Subscription {
164 | t, ok := c.subs.Load(id)
165 | if !ok {
166 | return &fakeSubscription{c: c, name: id}
167 | }
168 | return t.(Subscription)
169 | }
170 |
171 | type fakeTopic struct {
172 | Topic
173 | c *fakeClient
174 | name string
175 | subs []*fakeSubscription
176 | }
177 |
178 | func (t *fakeTopic) String() string {
179 | return t.name
180 | }
181 |
182 | func (t *fakeTopic) Publish(ctx context.Context, msg Message) PublishResult {
183 | for _, sub := range t.subs {
184 | if sub.topicID == t.name {
185 | sub.msgs <- msg
186 | }
187 | }
188 | return &fakePublishResult{}
189 | }
190 |
191 | type fakeSubscription struct {
192 | Subscription
193 | c *fakeClient
194 | name string
195 | topicID string
196 | msgs chan Message
197 | }
198 |
199 | func (s *fakeSubscription) Exists(_ context.Context) (bool, error) {
200 | _, ok := s.c.subs.Load(s.name)
201 | return ok, nil
202 | }
203 |
204 | func (s *fakeSubscription) Receive(ctx context.Context, f func(context.Context, Message)) error {
205 | for {
206 | select {
207 | case <-ctx.Done():
208 | return nil
209 | case msg, ok := <-s.msgs:
210 | if !ok {
211 | return nil
212 | }
213 | f(ctx, msg)
214 | }
215 | }
216 | }
217 |
218 | func (s *fakeSubscription) Delete(_ context.Context) error {
219 | s.c.subs.Delete(s.name)
220 | return nil
221 | }
222 |
223 | type fakeMessage struct {
224 | Message
225 | id string
226 | data []byte
227 | attributes map[string]string
228 | publishTime time.Time
229 | }
230 |
231 | func newFakeMessage(id string, data []byte, attributes map[string]string, publishTime time.Time) *fakeMessage {
232 | return &fakeMessage{
233 | id: id,
234 | data: data,
235 | attributes: attributes,
236 | publishTime: publishTime,
237 | }
238 | }
239 |
240 | func (m *fakeMessage) ID() string {
241 | return m.id
242 | }
243 |
244 | func (m *fakeMessage) Data() []byte {
245 | return m.data
246 | }
247 |
248 | func (m *fakeMessage) Attributes() map[string]string {
249 | return m.attributes
250 | }
251 |
252 | func (m *fakeMessage) PublishTime() time.Time {
253 | return m.publishTime
254 | }
255 |
256 | func (m *fakeMessage) Ack() {}
257 |
258 | func (m *fakeMessage) Nack() {}
259 |
260 | type fakePublishResult struct {
261 | PublishResult
262 | }
263 |
264 | func (r *fakePublishResult) Get(_ context.Context) (serverID string, err error) {
265 | return "", nil
266 | }
267 |
--------------------------------------------------------------------------------
/pubsub/psiface/structs.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package psiface
16 |
17 | import (
18 | "cloud.google.com/go/pubsub"
19 | )
20 |
21 | type SubscriptionConfig struct {
22 | pubsub.SubscriptionConfig
23 | Topic Topic // shadows pubsub.SubscriptionConfig's field
24 | }
25 |
--------------------------------------------------------------------------------
/storage/stiface/adapters.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package stiface
16 |
17 | import (
18 | "context"
19 |
20 | "cloud.google.com/go/storage"
21 | )
22 |
23 | // AdaptClient adapts a storage.Client so that it satisfies the Client
24 | // interface.
25 | func AdaptClient(c *storage.Client) Client {
26 | return client{c}
27 | }
28 |
29 | type (
30 | client struct{ *storage.Client }
31 | bucketHandle struct{ *storage.BucketHandle }
32 | objectHandle struct{ *storage.ObjectHandle }
33 | bucketIterator struct{ *storage.BucketIterator }
34 | objectIterator struct{ *storage.ObjectIterator }
35 | reader struct{ *storage.Reader }
36 | writer struct{ *storage.Writer }
37 | copier struct{ *storage.Copier }
38 | composer struct{ *storage.Composer }
39 | aclHandle struct{ *storage.ACLHandle }
40 | )
41 |
42 | func (client) embedToIncludeNewMethods() {}
43 | func (bucketHandle) embedToIncludeNewMethods() {}
44 | func (objectHandle) embedToIncludeNewMethods() {}
45 | func (bucketIterator) embedToIncludeNewMethods() {}
46 | func (objectIterator) embedToIncludeNewMethods() {}
47 | func (writer) embedToIncludeNewMethods() {}
48 | func (reader) embedToIncludeNewMethods() {}
49 | func (copier) embedToIncludeNewMethods() {}
50 | func (composer) embedToIncludeNewMethods() {}
51 | func (aclHandle) embedToIncludeNewMethods() {}
52 |
53 | func (c client) Bucket(name string) BucketHandle {
54 | return bucketHandle{c.Client.Bucket(name)}
55 | }
56 |
57 | func (c client) Buckets(ctx context.Context, projectID string) BucketIterator {
58 | return bucketIterator{c.Client.Buckets(ctx, projectID)}
59 | }
60 |
61 | func (b bucketHandle) Object(name string) ObjectHandle {
62 | return objectHandle{b.BucketHandle.Object(name)}
63 | }
64 |
65 | func (b bucketHandle) If(conds storage.BucketConditions) BucketHandle {
66 | return bucketHandle{b.BucketHandle.If(conds)}
67 | }
68 |
69 | func (b bucketHandle) Objects(ctx context.Context, q *storage.Query) ObjectIterator {
70 | return objectIterator{b.BucketHandle.Objects(ctx, q)}
71 | }
72 |
73 | func (b bucketHandle) DefaultObjectACL() ACLHandle {
74 | return aclHandle{b.BucketHandle.DefaultObjectACL()}
75 | }
76 |
77 | func (b bucketHandle) ACL() ACLHandle {
78 | return aclHandle{b.BucketHandle.ACL()}
79 | }
80 |
81 | func (b bucketHandle) UserProject(projectID string) BucketHandle {
82 | return bucketHandle{b.BucketHandle.UserProject(projectID)}
83 | }
84 |
85 | func (bi bucketIterator) SetPrefix(s string) {
86 | bi.BucketIterator.Prefix = s
87 | }
88 |
89 | func (o objectHandle) ACL() ACLHandle {
90 | return aclHandle{o.ObjectHandle.ACL()}
91 | }
92 |
93 | func (o objectHandle) Generation(gen int64) ObjectHandle {
94 | return objectHandle{o.ObjectHandle.Generation(gen)}
95 | }
96 |
97 | func (o objectHandle) If(conds storage.Conditions) ObjectHandle {
98 | return objectHandle{o.ObjectHandle.If(conds)}
99 | }
100 |
101 | func (o objectHandle) Key(encryptionKey []byte) ObjectHandle {
102 | return objectHandle{o.ObjectHandle.Key(encryptionKey)}
103 | }
104 |
105 | func (o objectHandle) ReadCompressed(compressed bool) ObjectHandle {
106 | return objectHandle{o.ObjectHandle.ReadCompressed(compressed)}
107 | }
108 |
109 | func (o objectHandle) NewReader(ctx context.Context) (Reader, error) {
110 | r, err := o.ObjectHandle.NewReader(ctx)
111 | if err != nil {
112 | return nil, err
113 | }
114 | return reader{r}, nil
115 | }
116 |
117 | func (o objectHandle) NewRangeReader(ctx context.Context, offset, length int64) (Reader, error) {
118 | r, err := o.ObjectHandle.NewRangeReader(ctx, offset, length)
119 | if err != nil {
120 | return nil, err
121 | }
122 | return reader{r}, nil
123 | }
124 |
125 | func (o objectHandle) NewWriter(ctx context.Context) Writer {
126 | return writer{o.ObjectHandle.NewWriter(ctx)}
127 | }
128 |
129 | func (o objectHandle) CopierFrom(src ObjectHandle) Copier {
130 | return copier{o.ObjectHandle.CopierFrom(src.(objectHandle).ObjectHandle)}
131 | }
132 |
133 | func (o objectHandle) ComposerFrom(srcs ...ObjectHandle) Composer {
134 | objs := make([]*storage.ObjectHandle, len(srcs))
135 | for i, s := range srcs {
136 | objs[i] = s.(objectHandle).ObjectHandle
137 | }
138 | return composer{o.ObjectHandle.ComposerFrom(objs...)}
139 | }
140 |
141 | func (w writer) ObjectAttrs() *storage.ObjectAttrs {
142 | return &w.Writer.ObjectAttrs
143 | }
144 |
145 | func (w writer) SetChunkSize(s int) {
146 | w.ChunkSize = s
147 | }
148 |
149 | func (w writer) SetProgressFunc(f func(int64)) {
150 | w.ProgressFunc = f
151 | }
152 |
153 | func (w writer) SetCRC32C(c uint32) {
154 | w.CRC32C = c
155 | w.SendCRC32C = true
156 | }
157 |
158 | func (c copier) ObjectAttrs() *storage.ObjectAttrs {
159 | return &c.Copier.ObjectAttrs
160 | }
161 |
162 | func (c copier) SetRewriteToken(t string) {
163 | c.RewriteToken = t
164 | }
165 |
166 | func (c copier) SetProgressFunc(f func(copiedBytes, totalBytes uint64)) {
167 | c.ProgressFunc = f
168 | }
169 |
170 | func (c copier) SetDestinationKMSKeyName(k string) {
171 | c.DestinationKMSKeyName = k
172 | }
173 |
174 | func (c composer) ObjectAttrs() *storage.ObjectAttrs {
175 | return &c.Composer.ObjectAttrs
176 | }
177 |
--------------------------------------------------------------------------------
/storage/stiface/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package stiface provides a set of interfaces for the types in
16 | // cloud.google.com/go/storage. These can be used to create mocks or other test
17 | // doubles. The package also provides adapters to enable the types of the
18 | // storage package to implement these interfaces.
19 | //
20 | // We do not recommend using mocks for most testing. Please read
21 | // https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html.
22 | //
23 | // Note: This package is in alpha. Some backwards-incompatible changes may occur.
24 | //
25 | // You must embed these interfaces to implement them:
26 | //
27 | // type ClientMock struct {
28 | // stiface.Client
29 | // ...
30 | // }
31 | //
32 | // This ensures that your implementations will not break when methods are added
33 | // to the interfaces.
34 | package stiface
35 |
--------------------------------------------------------------------------------
/storage/stiface/examples_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package stiface_test
16 |
17 | import (
18 | "context"
19 |
20 | "cloud.google.com/go/storage"
21 | "github.com/googleapis/google-cloud-go-testing/storage/stiface"
22 | )
23 |
24 | func Example_AdaptClient() {
25 | ctx := context.Background()
26 | c, err := storage.NewClient(ctx)
27 | if err != nil {
28 | // TODO: Handle error.
29 | }
30 | client := stiface.AdaptClient(c)
31 | w := client.Bucket("my-bucket").Object("my-object").NewWriter(ctx)
32 | w.ObjectAttrs().ContentType = "text/plain"
33 | // TODO: Use w.
34 | }
35 |
--------------------------------------------------------------------------------
/storage/stiface/interfaces.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package stiface
16 |
17 | import (
18 | "context"
19 | "io"
20 |
21 | "cloud.google.com/go/iam"
22 | "cloud.google.com/go/storage"
23 | "google.golang.org/api/iterator"
24 | )
25 |
26 | type Client interface {
27 | Bucket(name string) BucketHandle
28 | Buckets(ctx context.Context, projectID string) BucketIterator
29 | Close() error
30 |
31 | embedToIncludeNewMethods()
32 | }
33 |
34 | type ObjectHandle interface {
35 | ACL() ACLHandle
36 | Generation(int64) ObjectHandle
37 | If(storage.Conditions) ObjectHandle
38 | Key([]byte) ObjectHandle
39 | ReadCompressed(bool) ObjectHandle
40 | Attrs(context.Context) (*storage.ObjectAttrs, error)
41 | Update(context.Context, storage.ObjectAttrsToUpdate) (*storage.ObjectAttrs, error)
42 | NewReader(context.Context) (Reader, error)
43 | NewRangeReader(context.Context, int64, int64) (Reader, error)
44 | NewWriter(context.Context) Writer
45 | Delete(context.Context) error
46 | CopierFrom(ObjectHandle) Copier
47 | ComposerFrom(...ObjectHandle) Composer
48 |
49 | embedToIncludeNewMethods()
50 | }
51 |
52 | type BucketHandle interface {
53 | Create(context.Context, string, *storage.BucketAttrs) error
54 | Delete(context.Context) error
55 | DefaultObjectACL() ACLHandle
56 | Object(string) ObjectHandle
57 | Attrs(context.Context) (*storage.BucketAttrs, error)
58 | Update(context.Context, storage.BucketAttrsToUpdate) (*storage.BucketAttrs, error)
59 | If(storage.BucketConditions) BucketHandle
60 | Objects(context.Context, *storage.Query) ObjectIterator
61 | ACL() ACLHandle
62 | IAM() *iam.Handle
63 | UserProject(projectID string) BucketHandle
64 | Notifications(context.Context) (map[string]*storage.Notification, error)
65 | AddNotification(context.Context, *storage.Notification) (*storage.Notification, error)
66 | DeleteNotification(context.Context, string) error
67 | LockRetentionPolicy(context.Context) error
68 |
69 | embedToIncludeNewMethods()
70 | }
71 |
72 | type ObjectIterator interface {
73 | Next() (*storage.ObjectAttrs, error)
74 | PageInfo() *iterator.PageInfo
75 |
76 | embedToIncludeNewMethods()
77 | }
78 |
79 | type BucketIterator interface {
80 | SetPrefix(string)
81 | Next() (*storage.BucketAttrs, error)
82 | PageInfo() *iterator.PageInfo
83 |
84 | embedToIncludeNewMethods()
85 | }
86 |
87 | type ACLHandle interface {
88 | Delete(context.Context, storage.ACLEntity) error
89 | Set(context.Context, storage.ACLEntity, storage.ACLRole) error
90 | List(context.Context) ([]storage.ACLRule, error)
91 |
92 | embedToIncludeNewMethods()
93 | }
94 |
95 | type Reader interface {
96 | io.ReadCloser
97 | Size() int64
98 | Remain() int64
99 | ContentType() string
100 | ContentEncoding() string
101 | CacheControl() string
102 |
103 | embedToIncludeNewMethods()
104 | }
105 |
106 | type Writer interface {
107 | io.WriteCloser
108 | ObjectAttrs() *storage.ObjectAttrs
109 | SetChunkSize(int)
110 | SetProgressFunc(func(int64))
111 | SetCRC32C(uint32) // Sets both CRC32C and SendCRC32C.
112 | CloseWithError(err error) error
113 | Attrs() *storage.ObjectAttrs
114 |
115 | embedToIncludeNewMethods()
116 | }
117 |
118 | type Copier interface {
119 | ObjectAttrs() *storage.ObjectAttrs
120 | SetRewriteToken(string)
121 | SetProgressFunc(func(uint64, uint64))
122 | SetDestinationKMSKeyName(string)
123 | Run(context.Context) (*storage.ObjectAttrs, error)
124 |
125 | embedToIncludeNewMethods()
126 | }
127 |
128 | type Composer interface {
129 | ObjectAttrs() *storage.ObjectAttrs
130 | Run(context.Context) (*storage.ObjectAttrs, error)
131 |
132 | embedToIncludeNewMethods()
133 | }
134 |
--------------------------------------------------------------------------------
/storage/stiface/stiface_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package stiface
16 |
17 | import (
18 | "bytes"
19 | "context"
20 | "fmt"
21 | "io/ioutil"
22 | "os"
23 | "testing"
24 |
25 | "cloud.google.com/go/storage"
26 | )
27 |
28 | func TestIntegration(t *testing.T) {
29 | if testing.Short() {
30 | t.Skip("integration tests skipped in short mode")
31 | }
32 | name := os.Getenv("STIFACE_BUCKET")
33 | if name == "" {
34 | t.Skip("missing STIFACE_BUCKET environment variable")
35 | }
36 | ctx := context.Background()
37 | c, err := storage.NewClient(ctx)
38 | if err != nil {
39 | t.Fatal(err)
40 | }
41 | client := AdaptClient(c)
42 | defer client.Close()
43 | bkt := client.Bucket(name)
44 | basicTests(t, name, bkt)
45 | }
46 |
47 | func basicTests(t *testing.T, bucketName string, bkt BucketHandle) {
48 | ctx := context.Background()
49 | attrs, err := bkt.Attrs(ctx)
50 | if err != nil {
51 | t.Fatal(err)
52 | }
53 | if got, want := attrs.Name, bucketName; got != want {
54 | t.Errorf("name: got %v, want %v", got, want)
55 | }
56 |
57 | const contents = "hello, stiface"
58 | obj := bkt.Object("stiface-test")
59 | w := obj.NewWriter(ctx)
60 | if _, err := fmt.Fprint(w, contents); err != nil {
61 | t.Fatal(err)
62 | }
63 | if err := w.Close(); err != nil {
64 | t.Fatal(err)
65 | }
66 | bytes := readObject(t, obj)
67 | if got, want := string(bytes), contents; got != want {
68 | t.Errorf("got %q, want %q", got, want)
69 | }
70 | if err := obj.Delete(ctx); err != nil {
71 | t.Errorf("deleting: %v", err)
72 | }
73 | }
74 |
75 | func readObject(t *testing.T, obj ObjectHandle) []byte {
76 | r, err := obj.NewReader(context.Background())
77 | if err != nil {
78 | t.Fatalf("reading %v: %v", obj, err)
79 | }
80 | defer r.Close()
81 | bytes, err := ioutil.ReadAll(r)
82 | if err != nil {
83 | t.Fatalf("reading %v: %v", obj, err)
84 | }
85 | return bytes
86 | }
87 |
88 | // This test demonstrates how to use this package to create a simple fake for the storage client.
89 | func TestFake(t *testing.T) {
90 | ctx := context.Background()
91 | client := newFakeClient()
92 |
93 | bkt := client.Bucket("my-bucket")
94 | if err := bkt.Create(ctx, "my-project", nil); err != nil {
95 | t.Fatal(err)
96 | }
97 | basicTests(t, "my-bucket", bkt)
98 | }
99 |
100 | type fakeClient struct {
101 | Client
102 | buckets map[string]*fakeBucket
103 | }
104 |
105 | type fakeBucket struct {
106 | attrs *storage.BucketAttrs
107 | objects map[string][]byte
108 | }
109 |
110 | func newFakeClient() Client {
111 | return &fakeClient{buckets: map[string]*fakeBucket{}}
112 | }
113 |
114 | func (c *fakeClient) Bucket(name string) BucketHandle {
115 | return fakeBucketHandle{c: c, name: name}
116 | }
117 |
118 | type fakeBucketHandle struct {
119 | BucketHandle
120 | c *fakeClient
121 | name string
122 | }
123 |
124 | func (b fakeBucketHandle) Create(_ context.Context, _ string, attrs *storage.BucketAttrs) error {
125 | if _, ok := b.c.buckets[b.name]; ok {
126 | return fmt.Errorf("bucket %q already exists", b.name)
127 | }
128 | if attrs == nil {
129 | attrs = &storage.BucketAttrs{}
130 | }
131 | attrs.Name = b.name
132 | b.c.buckets[b.name] = &fakeBucket{attrs: attrs, objects: map[string][]byte{}}
133 | return nil
134 | }
135 |
136 | func (b fakeBucketHandle) Attrs(context.Context) (*storage.BucketAttrs, error) {
137 | bkt, ok := b.c.buckets[b.name]
138 | if !ok {
139 | return nil, fmt.Errorf("bucket %q does not exist", b.name)
140 | }
141 | return bkt.attrs, nil
142 | }
143 |
144 | func (b fakeBucketHandle) Object(name string) ObjectHandle {
145 | return fakeObjectHandle{c: b.c, bucketName: b.name, name: name}
146 | }
147 |
148 | type fakeObjectHandle struct {
149 | ObjectHandle
150 | c *fakeClient
151 | bucketName string
152 | name string
153 | }
154 |
155 | func (o fakeObjectHandle) NewReader(context.Context) (Reader, error) {
156 | bkt, ok := o.c.buckets[o.bucketName]
157 | if !ok {
158 | return nil, fmt.Errorf("bucket %q not found", o.bucketName)
159 | }
160 | contents, ok := bkt.objects[o.name]
161 | if !ok {
162 | return nil, fmt.Errorf("object %q not found in bucket %q", o.name, o.bucketName)
163 | }
164 | return fakeReader{r: bytes.NewReader(contents)}, nil
165 | }
166 |
167 | func (o fakeObjectHandle) Delete(context.Context) error {
168 | bkt, ok := o.c.buckets[o.bucketName]
169 | if !ok {
170 | return fmt.Errorf("bucket %q not found", o.bucketName)
171 | }
172 | delete(bkt.objects, o.name)
173 | return nil
174 | }
175 |
176 | type fakeReader struct {
177 | Reader
178 | r *bytes.Reader
179 | }
180 |
181 | func (r fakeReader) Read(buf []byte) (int, error) {
182 | return r.r.Read(buf)
183 | }
184 |
185 | func (r fakeReader) Close() error {
186 | return nil
187 | }
188 |
189 | func (o fakeObjectHandle) NewWriter(context.Context) Writer {
190 | return &fakeWriter{obj: o}
191 | }
192 |
193 | type fakeWriter struct {
194 | Writer
195 | obj fakeObjectHandle
196 | buf bytes.Buffer
197 | }
198 |
199 | func (w *fakeWriter) Write(data []byte) (int, error) {
200 | return w.buf.Write(data)
201 | }
202 |
203 | func (w *fakeWriter) Close() error {
204 | bkt, ok := w.obj.c.buckets[w.obj.bucketName]
205 | if !ok {
206 | return fmt.Errorf("bucket %q not found", w.obj.bucketName)
207 | }
208 | bkt.objects[w.obj.name] = w.buf.Bytes()
209 | return nil
210 | }
211 |
--------------------------------------------------------------------------------
/tools.go:
--------------------------------------------------------------------------------
1 | // +build tools
2 |
3 | // Copyright 2019 Google LLC
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | // This package exists to cause `go mod` and `go get` to believe these tools
18 | // are dependencies, even though they are not runtime dependencies of any
19 | // package (these are tools used by our CI builds). This means they will appear
20 | // in our `go.mod` file, but will not be a part of the build. Also, since the
21 | // build target is something non-existent, these should not be included in any
22 | // binaries.
23 |
24 | package cloud
25 |
26 | import (
27 | _ "golang.org/x/lint/golint"
28 | _ "golang.org/x/tools/cmd/goimports"
29 | _ "honnef.co/go/tools/cmd/staticcheck"
30 | )
31 |
--------------------------------------------------------------------------------