├── .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 | --------------------------------------------------------------------------------