mirror of https://github.com/dexidp/dex.git
25 changed files with 2054 additions and 11 deletions
@ -0,0 +1,39 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/coreos/poke/storage" |
||||
"github.com/kylelemons/godebug/pretty" |
||||
|
||||
yaml "gopkg.in/yaml.v2" |
||||
) |
||||
|
||||
func TestUnmarshalClients(t *testing.T) { |
||||
data := `staticClients: |
||||
- id: example-app |
||||
redirectURIs: |
||||
- 'http://127.0.0.1:5555/callback'
|
||||
name: 'Example App' |
||||
secret: ZXhhbXBsZS1hcHAtc2VjcmV0 |
||||
` |
||||
var c Config |
||||
if err := yaml.Unmarshal([]byte(data), &c); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
wantClients := []storage.Client{ |
||||
{ |
||||
ID: "example-app", |
||||
Name: "Example App", |
||||
Secret: "ZXhhbXBsZS1hcHAtc2VjcmV0", |
||||
RedirectURIs: []string{ |
||||
"http://127.0.0.1:5555/callback", |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
if diff := pretty.Compare(wantClients, c.StaticClients); diff != "" { |
||||
t.Errorf("did not get expected clients: %s", diff) |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@
|
||||
issuer: http://127.0.0.1:5556 |
||||
storage: |
||||
# NOTE(ericchiang): This will be replaced by sqlite3 in the future. |
||||
type: memory |
||||
|
||||
web: |
||||
http: 127.0.0.1:5556 |
||||
|
||||
connectors: |
||||
- type: mock |
||||
id: mock |
||||
name: Mock |
||||
- type: github |
||||
id: github |
||||
name: GitHub |
||||
config: |
||||
clientID: "$GITHUB_CLIENT_ID" |
||||
clientSecret: "$GITHUB_CLIENT_SECRET" |
||||
redirectURI: http://127.0.0.1:5556/callback/github |
||||
org: kubernetes |
||||
|
||||
staticClients: |
||||
- id: example-app |
||||
redirectURIs: |
||||
- 'http://127.0.0.1:5555/callback' |
||||
name: 'Example App' |
||||
secret: ZXhhbXBsZS1hcHAtc2VjcmV0 |
||||
@ -0,0 +1,48 @@
|
||||
package memory |
||||
|
||||
import ( |
||||
"reflect" |
||||
"testing" |
||||
|
||||
"github.com/coreos/poke/storage" |
||||
) |
||||
|
||||
func TestStaticClients(t *testing.T) { |
||||
s := New() |
||||
|
||||
c1 := storage.Client{ID: "foo", Secret: "foo_secret"} |
||||
c2 := storage.Client{ID: "bar", Secret: "bar_secret"} |
||||
s.CreateClient(c1) |
||||
s2 := storage.WithStaticClients(s, []storage.Client{c2}) |
||||
|
||||
tests := []struct { |
||||
id string |
||||
s storage.Storage |
||||
wantErr bool |
||||
wantClient storage.Client |
||||
}{ |
||||
{"foo", s, false, c1}, |
||||
{"bar", s, true, storage.Client{}}, |
||||
{"foo", s2, true, storage.Client{}}, |
||||
{"bar", s2, false, c2}, |
||||
} |
||||
|
||||
for i, tc := range tests { |
||||
gotClient, err := tc.s.GetClient(tc.id) |
||||
if err != nil { |
||||
if !tc.wantErr { |
||||
t.Errorf("case %d: GetClient(%q) %v", i, tc.id, err) |
||||
} |
||||
continue |
||||
} |
||||
|
||||
if tc.wantErr { |
||||
t.Errorf("case %d: GetClient(%q) expected error", i, tc.id) |
||||
continue |
||||
} |
||||
|
||||
if !reflect.DeepEqual(tc.wantClient, gotClient) { |
||||
t.Errorf("case %d: expected=%#v got=%#v", i, tc.wantClient, gotClient) |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,55 @@
|
||||
package storage |
||||
|
||||
import "errors" |
||||
|
||||
// Tests for this code are in the "memory" package, since this package doesn't
|
||||
// define a concrete storage implementation.
|
||||
|
||||
// staticClientsStorage is a storage that only allow read-only actions on clients.
|
||||
// All read actions return from the list of clients stored in memory, not the
|
||||
// underlying
|
||||
type staticClientsStorage struct { |
||||
Storage |
||||
|
||||
// A read-only set of clients.
|
||||
clients []Client |
||||
clientsByID map[string]Client |
||||
} |
||||
|
||||
// WithStaticClients returns a storage with a read-only set of clients. Write actions,
|
||||
// such as creating other clients, will fail.
|
||||
//
|
||||
// In the future the returned storage may allow creating and storing additional clients
|
||||
// in the underlying storage.
|
||||
func WithStaticClients(s Storage, staticClients []Client) Storage { |
||||
clientsByID := make(map[string]Client, len(staticClients)) |
||||
for _, client := range staticClients { |
||||
clientsByID[client.ID] = client |
||||
} |
||||
return staticClientsStorage{s, staticClients, clientsByID} |
||||
} |
||||
|
||||
func (s staticClientsStorage) GetClient(id string) (Client, error) { |
||||
if client, ok := s.clientsByID[id]; ok { |
||||
return client, nil |
||||
} |
||||
return Client{}, ErrNotFound |
||||
} |
||||
|
||||
func (s staticClientsStorage) ListClients() ([]Client, error) { |
||||
clients := make([]Client, len(s.clients)) |
||||
copy(clients, s.clients) |
||||
return clients, nil |
||||
} |
||||
|
||||
func (s staticClientsStorage) CreateClient(c Client) error { |
||||
return errors.New("static clients: read-only cannot create client") |
||||
} |
||||
|
||||
func (s staticClientsStorage) DeleteClient(id string) error { |
||||
return errors.New("static clients: read-only cannot delete client") |
||||
} |
||||
|
||||
func (s staticClientsStorage) UpdateClient(id string, updater func(old Client) (Client, error)) error { |
||||
return errors.New("static clients: read-only cannot update client") |
||||
} |
||||
@ -0,0 +1,5 @@
|
||||
language: go |
||||
|
||||
go: |
||||
- 1.6 |
||||
- tip |
||||
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, |
||||
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by |
||||
the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all |
||||
other entities that control, are controlled by, or are under common |
||||
control with that entity. For the purposes of this definition, |
||||
"control" means (i) the power, direct or indirect, to cause the |
||||
direction or management of such entity, whether by contract or |
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity |
||||
exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, |
||||
including but not limited to software source code, documentation |
||||
source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical |
||||
transformation or translation of a Source form, including but |
||||
not limited to compiled object code, generated documentation, |
||||
and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or |
||||
Object form, made available under the License, as indicated by a |
||||
copyright notice that is included in or attached to the work |
||||
(an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object |
||||
form, that is based on (or derived from) the Work and for which the |
||||
editorial revisions, annotations, elaborations, or other modifications |
||||
represent, as a whole, an original work of authorship. For the purposes |
||||
of this License, Derivative Works shall not include works that remain |
||||
separable from, or merely link (or bind by name) to the interfaces of, |
||||
the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including |
||||
the original version of the Work and any modifications or additions |
||||
to that Work or Derivative Works thereof, that is intentionally |
||||
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
or by an individual or Legal Entity authorized to submit on behalf of |
||||
the copyright owner. For the purposes of this definition, "submitted" |
||||
means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, |
||||
and issue tracking systems that are managed by, or on behalf of, the |
||||
Licensor for the purpose of discussing and improving the Work, but |
||||
excluding communication that is conspicuously marked or otherwise |
||||
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
on behalf of whom a Contribution has been received by Licensor and |
||||
subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the |
||||
Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
(except as stated in this section) patent license to make, have made, |
||||
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
where such license applies only to those patent claims licensable |
||||
by such Contributor that are necessarily infringed by their |
||||
Contribution(s) alone or by combination of their Contribution(s) |
||||
with the Work to which such Contribution(s) was submitted. If You |
||||
institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
or a Contribution incorporated within the Work constitutes direct |
||||
or contributory patent infringement, then any patent licenses |
||||
granted to You under this License for that Work shall terminate |
||||
as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the |
||||
Work or Derivative Works thereof in any medium, with or without |
||||
modifications, and in Source or Object form, provided that You |
||||
meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or |
||||
Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices |
||||
stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works |
||||
that You distribute, all copyright, patent, trademark, and |
||||
attribution notices from the Source form of the Work, |
||||
excluding those notices that do not pertain to any part of |
||||
the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its |
||||
distribution, then any Derivative Works that You distribute must |
||||
include a readable copy of the attribution notices contained |
||||
within such NOTICE file, excluding those notices that do not |
||||
pertain to any part of the Derivative Works, in at least one |
||||
of the following places: within a NOTICE text file distributed |
||||
as part of the Derivative Works; within the Source form or |
||||
documentation, if provided along with the Derivative Works; or, |
||||
within a display generated by the Derivative Works, if and |
||||
wherever such third-party notices normally appear. The contents |
||||
of the NOTICE file are for informational purposes only and |
||||
do not modify the License. You may add Your own attribution |
||||
notices within Derivative Works that You distribute, alongside |
||||
or as an addendum to the NOTICE text from the Work, provided |
||||
that such additional attribution notices cannot be construed |
||||
as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and |
||||
may provide additional or different license terms and conditions |
||||
for use, reproduction, or distribution of Your modifications, or |
||||
for any such Derivative Works as a whole, provided Your use, |
||||
reproduction, and distribution of the Work otherwise complies with |
||||
the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
any Contribution intentionally submitted for inclusion in the Work |
||||
by You to the Licensor shall be under the terms and conditions of |
||||
this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify |
||||
the terms of any separate license agreement you may have executed |
||||
with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade |
||||
names, trademarks, service marks, or product names of the Licensor, |
||||
except as required for reasonable and customary use in describing the |
||||
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
agreed to in writing, Licensor provides the Work (and each |
||||
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
implied, including, without limitation, any warranties or conditions |
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
appropriateness of using or redistributing the Work and assume any |
||||
risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, |
||||
whether in tort (including negligence), contract, or otherwise, |
||||
unless required by applicable law (such as deliberate and grossly |
||||
negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, |
||||
incidental, or consequential damages of any character arising as a |
||||
result of this License or out of the use or inability to use the |
||||
Work (including but not limited to damages for loss of goodwill, |
||||
work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses), even if such Contributor |
||||
has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing |
||||
the Work or Derivative Works thereof, You may choose to offer, |
||||
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
or other liability obligations and/or rights consistent with this |
||||
License. However, in accepting such obligations, You may act only |
||||
on Your own behalf and on Your sole responsibility, not on behalf |
||||
of any other Contributor, and only if You agree to indemnify, |
||||
defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason |
||||
of your accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
||||
@ -0,0 +1,64 @@
|
||||
Pretty Printing for Go |
||||
====================== |
||||
|
||||
[![godebug build status][ciimg]][ci] |
||||
|
||||
Have you ever wanted to get a pretty-printed version of a Go data structure, |
||||
complete with indentation? I have found this especially useful in unit tests |
||||
and in debugging my code, and thus godebug was born! |
||||
|
||||
[ciimg]: https://travis-ci.org/kylelemons/godebug.svg?branch=master |
||||
[ci]: https://travis-ci.org/kylelemons/godebug |
||||
|
||||
Quick Examples |
||||
-------------- |
||||
|
||||
By default, pretty will write out a very compact representation of a data structure. |
||||
From the [Print example][printex]: |
||||
|
||||
``` |
||||
{Name: "Spaceship Heart of Gold", |
||||
Crew: {Arthur Dent: "Along for the Ride", |
||||
Ford Prefect: "A Hoopy Frood", |
||||
Trillian: "Human", |
||||
Zaphod Beeblebrox: "Galactic President"}, |
||||
Androids: 1, |
||||
Stolen: true} |
||||
``` |
||||
|
||||
It can also produce a much more verbose, one-item-per-line representation suitable for |
||||
[computing diffs][diffex]. See the documentation for more examples and customization. |
||||
|
||||
[printex]: https://godoc.org/github.com/kylelemons/godebug/pretty#example-Print |
||||
[diffex]: https://godoc.org/github.com/kylelemons/godebug/pretty#example-Compare |
||||
|
||||
Documentation |
||||
------------- |
||||
|
||||
Documentation for this package is available at [godoc.org][doc]: |
||||
|
||||
* Pretty: [![godoc for godebug/pretty][prettyimg]][prettydoc] |
||||
* Diff: [![godoc for godebug/diff][diffimg]][diffdoc] |
||||
|
||||
[doc]: https://godoc.org/ |
||||
[prettyimg]: https://godoc.org/github.com/kylelemons/godebug/pretty?status.png |
||||
[prettydoc]: https://godoc.org/github.com/kylelemons/godebug/pretty |
||||
[diffimg]: https://godoc.org/github.com/kylelemons/godebug/diff?status.png |
||||
[diffdoc]: https://godoc.org/github.com/kylelemons/godebug/diff |
||||
|
||||
Installation |
||||
------------ |
||||
|
||||
These packages are available via `go get`: |
||||
|
||||
```bash |
||||
$ go get -u github.com/kylelemons/godebug/{pretty,diff} |
||||
``` |
||||
|
||||
Other Packages |
||||
-------------- |
||||
|
||||
If `godebug/pretty` is not granular enough, I highly recommend |
||||
checking out [go-spew][spew]. |
||||
|
||||
[spew]: http://godoc.org/github.com/davecgh/go-spew/spew |
||||
@ -0,0 +1,133 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package diff implements a linewise diff algorithm.
|
||||
package diff |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"strings" |
||||
) |
||||
|
||||
// Chunk represents a piece of the diff. A chunk will not have both added and
|
||||
// deleted lines. Equal lines are always after any added or deleted lines.
|
||||
// A Chunk may or may not have any lines in it, especially for the first or last
|
||||
// chunk in a computation.
|
||||
type Chunk struct { |
||||
Added []string |
||||
Deleted []string |
||||
Equal []string |
||||
} |
||||
|
||||
// Diff returns a string containing a line-by-line unified diff of the linewise
|
||||
// changes required to make A into B. Each line is prefixed with '+', '-', or
|
||||
// ' ' to indicate if it should be added, removed, or is correct respectively.
|
||||
func Diff(A, B string) string { |
||||
aLines := strings.Split(A, "\n") |
||||
bLines := strings.Split(B, "\n") |
||||
|
||||
chunks := DiffChunks(aLines, bLines) |
||||
|
||||
buf := new(bytes.Buffer) |
||||
for _, c := range chunks { |
||||
for _, line := range c.Added { |
||||
fmt.Fprintf(buf, "+%s\n", line) |
||||
} |
||||
for _, line := range c.Deleted { |
||||
fmt.Fprintf(buf, "-%s\n", line) |
||||
} |
||||
for _, line := range c.Equal { |
||||
fmt.Fprintf(buf, " %s\n", line) |
||||
} |
||||
} |
||||
return strings.TrimRight(buf.String(), "\n") |
||||
} |
||||
|
||||
// DiffChunks uses an O(D(N+M)) shortest-edit-script algorithm
|
||||
// to compute the edits required from A to B and returns the
|
||||
// edit chunks.
|
||||
func DiffChunks(A, B []string) []Chunk { |
||||
// algorithm: http://www.xmailserver.org/diff2.pdf
|
||||
|
||||
N, M := len(A), len(B) |
||||
MAX := N + M |
||||
V := make([]int, 2*MAX+1) |
||||
Vs := make([][]int, 0, 8) |
||||
|
||||
var D int |
||||
dLoop: |
||||
for D = 0; D <= MAX; D++ { |
||||
for k := -D; k <= D; k += 2 { |
||||
var x int |
||||
if k == -D || (k != D && V[MAX+k-1] < V[MAX+k+1]) { |
||||
x = V[MAX+k+1] |
||||
} else { |
||||
x = V[MAX+k-1] + 1 |
||||
} |
||||
y := x - k |
||||
for x < N && y < M && A[x] == B[y] { |
||||
x++ |
||||
y++ |
||||
} |
||||
V[MAX+k] = x |
||||
if x >= N && y >= M { |
||||
Vs = append(Vs, append(make([]int, 0, len(V)), V...)) |
||||
break dLoop |
||||
} |
||||
} |
||||
Vs = append(Vs, append(make([]int, 0, len(V)), V...)) |
||||
} |
||||
if D == 0 { |
||||
return nil |
||||
} |
||||
chunks := make([]Chunk, D+1) |
||||
|
||||
x, y := N, M |
||||
for d := D; d > 0; d-- { |
||||
V := Vs[d] |
||||
k := x - y |
||||
insert := k == -d || (k != d && V[MAX+k-1] < V[MAX+k+1]) |
||||
|
||||
x1 := V[MAX+k] |
||||
var x0, xM, kk int |
||||
if insert { |
||||
kk = k + 1 |
||||
x0 = V[MAX+kk] |
||||
xM = x0 |
||||
} else { |
||||
kk = k - 1 |
||||
x0 = V[MAX+kk] |
||||
xM = x0 + 1 |
||||
} |
||||
y0 := x0 - kk |
||||
|
||||
var c Chunk |
||||
if insert { |
||||
c.Added = B[y0:][:1] |
||||
} else { |
||||
c.Deleted = A[x0:][:1] |
||||
} |
||||
if xM < x1 { |
||||
c.Equal = A[xM:][:x1-xM] |
||||
} |
||||
|
||||
x, y = x0, y0 |
||||
chunks[d] = c |
||||
} |
||||
if x > 0 { |
||||
chunks[0].Equal = A[:x] |
||||
} |
||||
return chunks |
||||
} |
||||
@ -0,0 +1,120 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package diff |
||||
|
||||
import ( |
||||
"fmt" |
||||
"reflect" |
||||
"strings" |
||||
"testing" |
||||
) |
||||
|
||||
func TestDiff(t *testing.T) { |
||||
tests := []struct { |
||||
desc string |
||||
A, B []string |
||||
chunks []Chunk |
||||
}{ |
||||
{ |
||||
desc: "constitution", |
||||
A: []string{ |
||||
"We the People of the United States, in Order to form a more perfect Union,", |
||||
"establish Justice, insure domestic Tranquility, provide for the common defence,", |
||||
"and secure the Blessings of Liberty to ourselves", |
||||
"and our Posterity, do ordain and establish this Constitution for the United", |
||||
"States of America.", |
||||
}, |
||||
B: []string{ |
||||
"We the People of the United States, in Order to form a more perfect Union,", |
||||
"establish Justice, insure domestic Tranquility, provide for the common defence,", |
||||
"promote the general Welfare, and secure the Blessings of Liberty to ourselves", |
||||
"and our Posterity, do ordain and establish this Constitution for the United", |
||||
"States of America.", |
||||
}, |
||||
chunks: []Chunk{ |
||||
0: { |
||||
Equal: []string{ |
||||
"We the People of the United States, in Order to form a more perfect Union,", |
||||
"establish Justice, insure domestic Tranquility, provide for the common defence,", |
||||
}, |
||||
}, |
||||
1: { |
||||
Deleted: []string{ |
||||
"and secure the Blessings of Liberty to ourselves", |
||||
}, |
||||
}, |
||||
2: { |
||||
Added: []string{ |
||||
"promote the general Welfare, and secure the Blessings of Liberty to ourselves", |
||||
}, |
||||
Equal: []string{ |
||||
"and our Posterity, do ordain and establish this Constitution for the United", |
||||
"States of America.", |
||||
}, |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
got := DiffChunks(test.A, test.B) |
||||
if got, want := len(got), len(test.chunks); got != want { |
||||
t.Errorf("%s: edit distance = %v, want %v", test.desc, got-1, want-1) |
||||
continue |
||||
} |
||||
for i := range got { |
||||
got, want := got[i], test.chunks[i] |
||||
if got, want := got.Added, want.Added; !reflect.DeepEqual(got, want) { |
||||
t.Errorf("%s[%d]: Added = %v, want %v", test.desc, i, got, want) |
||||
} |
||||
if got, want := got.Deleted, want.Deleted; !reflect.DeepEqual(got, want) { |
||||
t.Errorf("%s[%d]: Deleted = %v, want %v", test.desc, i, got, want) |
||||
} |
||||
if got, want := got.Equal, want.Equal; !reflect.DeepEqual(got, want) { |
||||
t.Errorf("%s[%d]: Equal = %v, want %v", test.desc, i, got, want) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func ExampleDiff() { |
||||
constitution := strings.TrimSpace(` |
||||
We the People of the United States, in Order to form a more perfect Union, |
||||
establish Justice, insure domestic Tranquility, provide for the common defence, |
||||
promote the general Welfare, and secure the Blessings of Liberty to ourselves |
||||
and our Posterity, do ordain and establish this Constitution for the United |
||||
States of America. |
||||
`) |
||||
|
||||
got := strings.TrimSpace(` |
||||
:wq |
||||
We the People of the United States, in Order to form a more perfect Union, |
||||
establish Justice, insure domestic Tranquility, provide for the common defence, |
||||
and secure the Blessings of Liberty to ourselves |
||||
and our Posterity, do ordain and establish this Constitution for the United |
||||
States of America. |
||||
`) |
||||
|
||||
fmt.Println(Diff(got, constitution)) |
||||
|
||||
// Output:
|
||||
// -:wq
|
||||
// We the People of the United States, in Order to form a more perfect Union,
|
||||
// establish Justice, insure domestic Tranquility, provide for the common defence,
|
||||
// -and secure the Blessings of Liberty to ourselves
|
||||
// +promote the general Welfare, and secure the Blessings of Liberty to ourselves
|
||||
// and our Posterity, do ordain and establish this Constitution for the United
|
||||
// States of America.
|
||||
} |
||||
@ -0,0 +1,5 @@
|
||||
*.test |
||||
*.bench |
||||
*.golden |
||||
*.txt |
||||
*.prof |
||||
@ -0,0 +1,25 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package pretty pretty-prints Go structures.
|
||||
//
|
||||
// This package uses reflection to examine a Go value and can
|
||||
// print out in a nice, aligned fashion. It supports three
|
||||
// modes (normal, compact, and extended) for advanced use.
|
||||
//
|
||||
// See the Reflect and Print examples for what the output looks like.
|
||||
package pretty |
||||
|
||||
// TODO:
|
||||
// - Catch cycles
|
||||
@ -0,0 +1,281 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pretty_test |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/kylelemons/godebug/pretty" |
||||
) |
||||
|
||||
func ExampleConfig_Sprint() { |
||||
type Pair [2]int |
||||
type Map struct { |
||||
Name string |
||||
Players map[string]Pair |
||||
Obstacles map[Pair]string |
||||
} |
||||
|
||||
m := Map{ |
||||
Name: "Rock Creek", |
||||
Players: map[string]Pair{ |
||||
"player1": {1, 3}, |
||||
"player2": {0, -1}, |
||||
}, |
||||
Obstacles: map[Pair]string{ |
||||
Pair{0, 0}: "rock", |
||||
Pair{2, 1}: "pond", |
||||
Pair{1, 1}: "stream", |
||||
Pair{0, 1}: "stream", |
||||
}, |
||||
} |
||||
|
||||
// Specific output formats
|
||||
compact := &pretty.Config{ |
||||
Compact: true, |
||||
} |
||||
diffable := &pretty.Config{ |
||||
Diffable: true, |
||||
} |
||||
|
||||
// Print out a summary
|
||||
fmt.Printf("Players: %s\n", compact.Sprint(m.Players)) |
||||
|
||||
// Print diffable output
|
||||
fmt.Printf("Map State:\n%s", diffable.Sprint(m)) |
||||
|
||||
// Output:
|
||||
// Players: {player1:[1,3],player2:[0,-1]}
|
||||
// Map State:
|
||||
// {
|
||||
// Name: "Rock Creek",
|
||||
// Players: {
|
||||
// player1: [
|
||||
// 1,
|
||||
// 3,
|
||||
// ],
|
||||
// player2: [
|
||||
// 0,
|
||||
// -1,
|
||||
// ],
|
||||
// },
|
||||
// Obstacles: {
|
||||
// [0,0]: "rock",
|
||||
// [0,1]: "stream",
|
||||
// [1,1]: "stream",
|
||||
// [2,1]: "pond",
|
||||
// },
|
||||
// }
|
||||
} |
||||
|
||||
func ExamplePrint() { |
||||
type ShipManifest struct { |
||||
Name string |
||||
Crew map[string]string |
||||
Androids int |
||||
Stolen bool |
||||
} |
||||
|
||||
manifest := &ShipManifest{ |
||||
Name: "Spaceship Heart of Gold", |
||||
Crew: map[string]string{ |
||||
"Zaphod Beeblebrox": "Galactic President", |
||||
"Trillian": "Human", |
||||
"Ford Prefect": "A Hoopy Frood", |
||||
"Arthur Dent": "Along for the Ride", |
||||
}, |
||||
Androids: 1, |
||||
Stolen: true, |
||||
} |
||||
|
||||
pretty.Print(manifest) |
||||
|
||||
// Output:
|
||||
// {Name: "Spaceship Heart of Gold",
|
||||
// Crew: {Arthur Dent: "Along for the Ride",
|
||||
// Ford Prefect: "A Hoopy Frood",
|
||||
// Trillian: "Human",
|
||||
// Zaphod Beeblebrox: "Galactic President"},
|
||||
// Androids: 1,
|
||||
// Stolen: true}
|
||||
} |
||||
|
||||
var t = struct { |
||||
Errorf func(string, ...interface{}) |
||||
}{ |
||||
Errorf: func(format string, args ...interface{}) { |
||||
fmt.Println(fmt.Sprintf(format, args...) + "\n") |
||||
}, |
||||
} |
||||
|
||||
func ExampleCompare_testing() { |
||||
// Code under test:
|
||||
|
||||
type ShipManifest struct { |
||||
Name string |
||||
Crew map[string]string |
||||
Androids int |
||||
Stolen bool |
||||
} |
||||
|
||||
// AddCrew tries to add the given crewmember to the manifest.
|
||||
AddCrew := func(m *ShipManifest, name, title string) { |
||||
if m.Crew == nil { |
||||
m.Crew = make(map[string]string) |
||||
} |
||||
m.Crew[title] = name |
||||
} |
||||
|
||||
// Test function:
|
||||
tests := []struct { |
||||
desc string |
||||
before *ShipManifest |
||||
name, title string |
||||
after *ShipManifest |
||||
}{ |
||||
{ |
||||
desc: "add first", |
||||
before: &ShipManifest{}, |
||||
name: "Zaphod Beeblebrox", |
||||
title: "Galactic President", |
||||
after: &ShipManifest{ |
||||
Crew: map[string]string{ |
||||
"Zaphod Beeblebrox": "Galactic President", |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
desc: "add another", |
||||
before: &ShipManifest{ |
||||
Crew: map[string]string{ |
||||
"Zaphod Beeblebrox": "Galactic President", |
||||
}, |
||||
}, |
||||
name: "Trillian", |
||||
title: "Human", |
||||
after: &ShipManifest{ |
||||
Crew: map[string]string{ |
||||
"Zaphod Beeblebrox": "Galactic President", |
||||
"Trillian": "Human", |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
desc: "overwrite", |
||||
before: &ShipManifest{ |
||||
Crew: map[string]string{ |
||||
"Zaphod Beeblebrox": "Galactic President", |
||||
}, |
||||
}, |
||||
name: "Zaphod Beeblebrox", |
||||
title: "Just this guy, you know?", |
||||
after: &ShipManifest{ |
||||
Crew: map[string]string{ |
||||
"Zaphod Beeblebrox": "Just this guy, you know?", |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
AddCrew(test.before, test.name, test.title) |
||||
if diff := pretty.Compare(test.before, test.after); diff != "" { |
||||
t.Errorf("%s: post-AddCrew diff: (-got +want)\n%s", test.desc, diff) |
||||
} |
||||
} |
||||
|
||||
// Output:
|
||||
// add first: post-AddCrew diff: (-got +want)
|
||||
// {
|
||||
// Name: "",
|
||||
// Crew: {
|
||||
// - Galactic President: "Zaphod Beeblebrox",
|
||||
// + Zaphod Beeblebrox: "Galactic President",
|
||||
// },
|
||||
// Androids: 0,
|
||||
// Stolen: false,
|
||||
// }
|
||||
//
|
||||
// add another: post-AddCrew diff: (-got +want)
|
||||
// {
|
||||
// Name: "",
|
||||
// Crew: {
|
||||
// - Human: "Trillian",
|
||||
// + Trillian: "Human",
|
||||
// Zaphod Beeblebrox: "Galactic President",
|
||||
// },
|
||||
// Androids: 0,
|
||||
// Stolen: false,
|
||||
// }
|
||||
//
|
||||
// overwrite: post-AddCrew diff: (-got +want)
|
||||
// {
|
||||
// Name: "",
|
||||
// Crew: {
|
||||
// - Just this guy, you know?: "Zaphod Beeblebrox",
|
||||
// - Zaphod Beeblebrox: "Galactic President",
|
||||
// + Zaphod Beeblebrox: "Just this guy, you know?",
|
||||
// },
|
||||
// Androids: 0,
|
||||
// Stolen: false,
|
||||
// }
|
||||
} |
||||
|
||||
func ExampleCompare_debugging() { |
||||
type ShipManifest struct { |
||||
Name string |
||||
Crew map[string]string |
||||
Androids int |
||||
Stolen bool |
||||
} |
||||
|
||||
reported := &ShipManifest{ |
||||
Name: "Spaceship Heart of Gold", |
||||
Crew: map[string]string{ |
||||
"Zaphod Beeblebrox": "Galactic President", |
||||
"Trillian": "Human", |
||||
"Ford Prefect": "A Hoopy Frood", |
||||
"Arthur Dent": "Along for the Ride", |
||||
}, |
||||
Androids: 1, |
||||
Stolen: true, |
||||
} |
||||
|
||||
expected := &ShipManifest{ |
||||
Name: "Spaceship Heart of Gold", |
||||
Crew: map[string]string{ |
||||
"Trillian": "Human", |
||||
"Rowan Artosok": "Captain", |
||||
}, |
||||
Androids: 1, |
||||
Stolen: false, |
||||
} |
||||
|
||||
fmt.Println(pretty.Compare(reported, expected)) |
||||
// Output:
|
||||
// {
|
||||
// Name: "Spaceship Heart of Gold",
|
||||
// Crew: {
|
||||
// - Arthur Dent: "Along for the Ride",
|
||||
// - Ford Prefect: "A Hoopy Frood",
|
||||
// + Rowan Artosok: "Captain",
|
||||
// Trillian: "Human",
|
||||
// - Zaphod Beeblebrox: "Galactic President",
|
||||
// },
|
||||
// Androids: 1,
|
||||
// - Stolen: true,
|
||||
// + Stolen: false,
|
||||
// }
|
||||
} |
||||
@ -0,0 +1,116 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pretty |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"io" |
||||
"reflect" |
||||
|
||||
"github.com/kylelemons/godebug/diff" |
||||
) |
||||
|
||||
// A Config represents optional configuration parameters for formatting.
|
||||
//
|
||||
// Some options, notably ShortList, dramatically increase the overhead
|
||||
// of pretty-printing a value.
|
||||
type Config struct { |
||||
// Verbosity options
|
||||
Compact bool // One-line output. Overrides Diffable.
|
||||
Diffable bool // Adds extra newlines for more easily diffable output.
|
||||
|
||||
// Field and value options
|
||||
IncludeUnexported bool // Include unexported fields in output
|
||||
PrintStringers bool // Call String on a fmt.Stringer
|
||||
PrintTextMarshalers bool // Call MarshalText on an encoding.TextMarshaler
|
||||
SkipZeroFields bool // Skip struct fields that have a zero value.
|
||||
|
||||
// Output transforms
|
||||
ShortList int // Maximum character length for short lists if nonzero.
|
||||
} |
||||
|
||||
// Default Config objects
|
||||
var ( |
||||
// CompareConfig is the default configuration used for Compare.
|
||||
CompareConfig = &Config{ |
||||
Diffable: true, |
||||
IncludeUnexported: true, |
||||
} |
||||
|
||||
// DefaultConfig is the default configuration used for all other top-level functions.
|
||||
DefaultConfig = &Config{} |
||||
) |
||||
|
||||
func (cfg *Config) fprint(buf *bytes.Buffer, vals ...interface{}) { |
||||
for i, val := range vals { |
||||
if i > 0 { |
||||
buf.WriteByte('\n') |
||||
} |
||||
cfg.val2node(reflect.ValueOf(val)).WriteTo(buf, "", cfg) |
||||
} |
||||
} |
||||
|
||||
// Print writes the DefaultConfig representation of the given values to standard output.
|
||||
func Print(vals ...interface{}) { |
||||
DefaultConfig.Print(vals...) |
||||
} |
||||
|
||||
// Print writes the configured presentation of the given values to standard output.
|
||||
func (cfg *Config) Print(vals ...interface{}) { |
||||
fmt.Println(cfg.Sprint(vals...)) |
||||
} |
||||
|
||||
// Sprint returns a string representation of the given value according to the DefaultConfig.
|
||||
func Sprint(vals ...interface{}) string { |
||||
return DefaultConfig.Sprint(vals...) |
||||
} |
||||
|
||||
// Sprint returns a string representation of the given value according to cfg.
|
||||
func (cfg *Config) Sprint(vals ...interface{}) string { |
||||
buf := new(bytes.Buffer) |
||||
cfg.fprint(buf, vals...) |
||||
return buf.String() |
||||
} |
||||
|
||||
// Fprint writes the representation of the given value to the writer according to the DefaultConfig.
|
||||
func Fprint(w io.Writer, vals ...interface{}) (n int64, err error) { |
||||
return DefaultConfig.Fprint(w, vals...) |
||||
} |
||||
|
||||
// Fprint writes the representation of the given value to the writer according to the cfg.
|
||||
func (cfg *Config) Fprint(w io.Writer, vals ...interface{}) (n int64, err error) { |
||||
buf := new(bytes.Buffer) |
||||
cfg.fprint(buf, vals...) |
||||
return buf.WriteTo(w) |
||||
} |
||||
|
||||
// Compare returns a string containing a line-by-line unified diff of the
|
||||
// values in got and want, using the CompareConfig.
|
||||
//
|
||||
// Each line in the output is prefixed with '+', '-', or ' ' to indicate if it
|
||||
// should be added to, removed from, or is correct for the "got" value with
|
||||
// respect to the "want" value.
|
||||
func Compare(got, want interface{}) string { |
||||
return CompareConfig.Compare(got, want) |
||||
} |
||||
|
||||
// Compare returns a string containing a line-by-line unified diff of the
|
||||
// values in got and want according to the cfg.
|
||||
func (cfg *Config) Compare(got, want interface{}) string { |
||||
diffCfg := *cfg |
||||
diffCfg.Diffable = true |
||||
return diff.Diff(cfg.Sprint(got), cfg.Sprint(want)) |
||||
} |
||||
@ -0,0 +1,128 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pretty |
||||
|
||||
import ( |
||||
"testing" |
||||
) |
||||
|
||||
func TestDiff(t *testing.T) { |
||||
type example struct { |
||||
Name string |
||||
Age int |
||||
Friends []string |
||||
} |
||||
|
||||
tests := []struct { |
||||
desc string |
||||
got, want interface{} |
||||
diff string |
||||
}{ |
||||
{ |
||||
desc: "basic struct", |
||||
got: example{ |
||||
Name: "Zaphd", |
||||
Age: 42, |
||||
Friends: []string{ |
||||
"Ford Prefect", |
||||
"Trillian", |
||||
"Marvin", |
||||
}, |
||||
}, |
||||
want: example{ |
||||
Name: "Zaphod", |
||||
Age: 42, |
||||
Friends: []string{ |
||||
"Ford Prefect", |
||||
"Trillian", |
||||
}, |
||||
}, |
||||
diff: ` { |
||||
- Name: "Zaphd", |
||||
+ Name: "Zaphod", |
||||
Age: 42, |
||||
Friends: [ |
||||
"Ford Prefect", |
||||
"Trillian", |
||||
- "Marvin", |
||||
], |
||||
}`, |
||||
}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
if got, want := Compare(test.got, test.want), test.diff; got != want { |
||||
t.Errorf("%s:", test.desc) |
||||
t.Errorf(" got: %q", got) |
||||
t.Errorf(" want: %q", want) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestSkipZeroFields(t *testing.T) { |
||||
type example struct { |
||||
Name string |
||||
Species string |
||||
Age int |
||||
Friends []string |
||||
} |
||||
|
||||
tests := []struct { |
||||
desc string |
||||
got, want interface{} |
||||
diff string |
||||
}{ |
||||
{ |
||||
desc: "basic struct", |
||||
got: example{ |
||||
Name: "Zaphd", |
||||
Species: "Betelgeusian", |
||||
Age: 42, |
||||
}, |
||||
want: example{ |
||||
Name: "Zaphod", |
||||
Species: "Betelgeusian", |
||||
Age: 42, |
||||
Friends: []string{ |
||||
"Ford Prefect", |
||||
"Trillian", |
||||
"", |
||||
}, |
||||
}, |
||||
diff: ` { |
||||
- Name: "Zaphd", |
||||
+ Name: "Zaphod", |
||||
Species: "Betelgeusian", |
||||
Age: 42, |
||||
+ Friends: [ |
||||
+ "Ford Prefect", |
||||
+ "Trillian", |
||||
+ "", |
||||
+ ], |
||||
}`, |
||||
}, |
||||
} |
||||
|
||||
cfg := *CompareConfig |
||||
cfg.SkipZeroFields = true |
||||
|
||||
for _, test := range tests { |
||||
if got, want := cfg.Compare(test.got, test.want), test.diff; got != want { |
||||
t.Errorf("%s:", test.desc) |
||||
t.Errorf(" got: %q", got) |
||||
t.Errorf(" want: %q", want) |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,114 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pretty |
||||
|
||||
import ( |
||||
"encoding" |
||||
"fmt" |
||||
"reflect" |
||||
"sort" |
||||
) |
||||
|
||||
func isZeroVal(val reflect.Value) bool { |
||||
if !val.CanInterface() { |
||||
return false |
||||
} |
||||
z := reflect.Zero(val.Type()).Interface() |
||||
return reflect.DeepEqual(val.Interface(), z) |
||||
} |
||||
|
||||
func (c *Config) val2node(val reflect.Value) node { |
||||
// TODO(kevlar): pointer tracking?
|
||||
|
||||
if !val.IsValid() { |
||||
return rawVal("nil") |
||||
} |
||||
|
||||
if val.CanInterface() { |
||||
v := val.Interface() |
||||
if s, ok := v.(fmt.Stringer); ok && c.PrintStringers { |
||||
return stringVal(s.String()) |
||||
} |
||||
if t, ok := v.(encoding.TextMarshaler); ok && c.PrintTextMarshalers { |
||||
if raw, err := t.MarshalText(); err == nil { // if NOT an error
|
||||
return stringVal(string(raw)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
switch kind := val.Kind(); kind { |
||||
case reflect.Ptr, reflect.Interface: |
||||
if val.IsNil() { |
||||
return rawVal("nil") |
||||
} |
||||
return c.val2node(val.Elem()) |
||||
case reflect.String: |
||||
return stringVal(val.String()) |
||||
case reflect.Slice, reflect.Array: |
||||
n := list{} |
||||
length := val.Len() |
||||
for i := 0; i < length; i++ { |
||||
n = append(n, c.val2node(val.Index(i))) |
||||
} |
||||
return n |
||||
case reflect.Map: |
||||
n := keyvals{} |
||||
keys := val.MapKeys() |
||||
for _, key := range keys { |
||||
// TODO(kevlar): Support arbitrary type keys?
|
||||
n = append(n, keyval{compactString(c.val2node(key)), c.val2node(val.MapIndex(key))}) |
||||
} |
||||
sort.Sort(n) |
||||
return n |
||||
case reflect.Struct: |
||||
n := keyvals{} |
||||
typ := val.Type() |
||||
fields := typ.NumField() |
||||
for i := 0; i < fields; i++ { |
||||
sf := typ.Field(i) |
||||
if !c.IncludeUnexported && sf.PkgPath != "" { |
||||
continue |
||||
} |
||||
field := val.Field(i) |
||||
if c.SkipZeroFields && isZeroVal(field) { |
||||
continue |
||||
} |
||||
n = append(n, keyval{sf.Name, c.val2node(field)}) |
||||
} |
||||
return n |
||||
case reflect.Bool: |
||||
if val.Bool() { |
||||
return rawVal("true") |
||||
} |
||||
return rawVal("false") |
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
||||
return rawVal(fmt.Sprintf("%d", val.Int())) |
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
||||
return rawVal(fmt.Sprintf("%d", val.Uint())) |
||||
case reflect.Uintptr: |
||||
return rawVal(fmt.Sprintf("0x%X", val.Uint())) |
||||
case reflect.Float32, reflect.Float64: |
||||
return rawVal(fmt.Sprintf("%v", val.Float())) |
||||
case reflect.Complex64, reflect.Complex128: |
||||
return rawVal(fmt.Sprintf("%v", val.Complex())) |
||||
} |
||||
|
||||
// Fall back to the default %#v if we can
|
||||
if val.CanInterface() { |
||||
return rawVal(fmt.Sprintf("%#v", val.Interface())) |
||||
} |
||||
|
||||
return rawVal(val.String()) |
||||
} |
||||
@ -0,0 +1,168 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pretty |
||||
|
||||
import ( |
||||
"reflect" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func TestVal2nodeDefault(t *testing.T) { |
||||
tests := []struct { |
||||
desc string |
||||
raw interface{} |
||||
want node |
||||
}{ |
||||
{ |
||||
desc: "nil", |
||||
raw: nil, |
||||
want: rawVal("nil"), |
||||
}, |
||||
{ |
||||
desc: "nil ptr", |
||||
raw: (*int)(nil), |
||||
want: rawVal("nil"), |
||||
}, |
||||
{ |
||||
desc: "nil slice", |
||||
raw: []string(nil), |
||||
want: list{}, |
||||
}, |
||||
{ |
||||
desc: "nil map", |
||||
raw: map[string]string(nil), |
||||
want: keyvals{}, |
||||
}, |
||||
{ |
||||
desc: "string", |
||||
raw: "zaphod", |
||||
want: stringVal("zaphod"), |
||||
}, |
||||
{ |
||||
desc: "slice", |
||||
raw: []string{"a", "b"}, |
||||
want: list{stringVal("a"), stringVal("b")}, |
||||
}, |
||||
{ |
||||
desc: "map", |
||||
raw: map[string]string{ |
||||
"zaphod": "beeblebrox", |
||||
"ford": "prefect", |
||||
}, |
||||
want: keyvals{ |
||||
{"ford", stringVal("prefect")}, |
||||
{"zaphod", stringVal("beeblebrox")}, |
||||
}, |
||||
}, |
||||
{ |
||||
desc: "map of [2]int", |
||||
raw: map[[2]int]string{ |
||||
[2]int{-1, 2}: "school", |
||||
[2]int{0, 0}: "origin", |
||||
[2]int{1, 3}: "home", |
||||
}, |
||||
want: keyvals{ |
||||
{"[-1,2]", stringVal("school")}, |
||||
{"[0,0]", stringVal("origin")}, |
||||
{"[1,3]", stringVal("home")}, |
||||
}, |
||||
}, |
||||
{ |
||||
desc: "struct", |
||||
raw: struct{ Zaphod, Ford string }{"beeblebrox", "prefect"}, |
||||
want: keyvals{ |
||||
{"Zaphod", stringVal("beeblebrox")}, |
||||
{"Ford", stringVal("prefect")}, |
||||
}, |
||||
}, |
||||
{ |
||||
desc: "int", |
||||
raw: 3, |
||||
want: rawVal("3"), |
||||
}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
if got, want := DefaultConfig.val2node(reflect.ValueOf(test.raw)), test.want; !reflect.DeepEqual(got, want) { |
||||
t.Errorf("%s: got %#v, want %#v", test.desc, got, want) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestVal2node(t *testing.T) { |
||||
tests := []struct { |
||||
desc string |
||||
raw interface{} |
||||
cfg *Config |
||||
want node |
||||
}{ |
||||
{ |
||||
desc: "struct default", |
||||
raw: struct{ Zaphod, Ford, foo string }{"beeblebrox", "prefect", "BAD"}, |
||||
cfg: DefaultConfig, |
||||
want: keyvals{ |
||||
{"Zaphod", stringVal("beeblebrox")}, |
||||
{"Ford", stringVal("prefect")}, |
||||
}, |
||||
}, |
||||
{ |
||||
desc: "struct w/ IncludeUnexported", |
||||
raw: struct{ Zaphod, Ford, foo string }{"beeblebrox", "prefect", "GOOD"}, |
||||
cfg: &Config{ |
||||
IncludeUnexported: true, |
||||
}, |
||||
want: keyvals{ |
||||
{"Zaphod", stringVal("beeblebrox")}, |
||||
{"Ford", stringVal("prefect")}, |
||||
{"foo", stringVal("GOOD")}, |
||||
}, |
||||
}, |
||||
{ |
||||
desc: "time default", |
||||
raw: struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()}, |
||||
cfg: DefaultConfig, |
||||
want: keyvals{ |
||||
{"Date", keyvals{}}, |
||||
}, |
||||
}, |
||||
{ |
||||
desc: "time w/ PrintTextMarshalers", |
||||
raw: struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()}, |
||||
cfg: &Config{ |
||||
PrintTextMarshalers: true, |
||||
}, |
||||
want: keyvals{ |
||||
{"Date", stringVal("2009-02-13T23:31:30Z")}, |
||||
}, |
||||
}, |
||||
{ |
||||
desc: "time w/ PrintStringers", |
||||
raw: struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()}, |
||||
cfg: &Config{ |
||||
PrintStringers: true, |
||||
}, |
||||
want: keyvals{ |
||||
{"Date", stringVal("2009-02-13 23:31:30 +0000 UTC")}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
if got, want := test.cfg.val2node(reflect.ValueOf(test.raw)), test.want; !reflect.DeepEqual(got, want) { |
||||
t.Errorf("%s: got %#v, want %#v", test.desc, got, want) |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,160 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pretty |
||||
|
||||
import ( |
||||
"bytes" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
type node interface { |
||||
WriteTo(w *bytes.Buffer, indent string, cfg *Config) |
||||
} |
||||
|
||||
func compactString(n node) string { |
||||
switch k := n.(type) { |
||||
case stringVal: |
||||
return string(k) |
||||
case rawVal: |
||||
return string(k) |
||||
} |
||||
|
||||
buf := new(bytes.Buffer) |
||||
n.WriteTo(buf, "", &Config{Compact: true}) |
||||
return buf.String() |
||||
} |
||||
|
||||
type stringVal string |
||||
|
||||
func (str stringVal) WriteTo(w *bytes.Buffer, indent string, cfg *Config) { |
||||
w.WriteString(strconv.Quote(string(str))) |
||||
} |
||||
|
||||
type rawVal string |
||||
|
||||
func (r rawVal) WriteTo(w *bytes.Buffer, indent string, cfg *Config) { |
||||
w.WriteString(string(r)) |
||||
} |
||||
|
||||
type keyval struct { |
||||
key string |
||||
val node |
||||
} |
||||
|
||||
type keyvals []keyval |
||||
|
||||
func (l keyvals) Len() int { return len(l) } |
||||
func (l keyvals) Swap(i, j int) { l[i], l[j] = l[j], l[i] } |
||||
func (l keyvals) Less(i, j int) bool { return l[i].key < l[j].key } |
||||
|
||||
func (l keyvals) WriteTo(w *bytes.Buffer, indent string, cfg *Config) { |
||||
w.WriteByte('{') |
||||
|
||||
switch { |
||||
case cfg.Compact: |
||||
// All on one line:
|
||||
for i, kv := range l { |
||||
if i > 0 { |
||||
w.WriteByte(',') |
||||
} |
||||
w.WriteString(kv.key) |
||||
w.WriteByte(':') |
||||
kv.val.WriteTo(w, indent, cfg) |
||||
} |
||||
case cfg.Diffable: |
||||
w.WriteByte('\n') |
||||
inner := indent + " " |
||||
// Each value gets its own line:
|
||||
for _, kv := range l { |
||||
w.WriteString(inner) |
||||
w.WriteString(kv.key) |
||||
w.WriteString(": ") |
||||
kv.val.WriteTo(w, inner, cfg) |
||||
w.WriteString(",\n") |
||||
} |
||||
w.WriteString(indent) |
||||
default: |
||||
keyWidth := 0 |
||||
for _, kv := range l { |
||||
if kw := len(kv.key); kw > keyWidth { |
||||
keyWidth = kw |
||||
} |
||||
} |
||||
alignKey := indent + " " |
||||
alignValue := strings.Repeat(" ", keyWidth) |
||||
inner := alignKey + alignValue + " " |
||||
// First and last line shared with bracket:
|
||||
for i, kv := range l { |
||||
if i > 0 { |
||||
w.WriteString(",\n") |
||||
w.WriteString(alignKey) |
||||
} |
||||
w.WriteString(kv.key) |
||||
w.WriteString(": ") |
||||
w.WriteString(alignValue[len(kv.key):]) |
||||
kv.val.WriteTo(w, inner, cfg) |
||||
} |
||||
} |
||||
|
||||
w.WriteByte('}') |
||||
} |
||||
|
||||
type list []node |
||||
|
||||
func (l list) WriteTo(w *bytes.Buffer, indent string, cfg *Config) { |
||||
if max := cfg.ShortList; max > 0 { |
||||
short := compactString(l) |
||||
if len(short) <= max { |
||||
w.WriteString(short) |
||||
return |
||||
} |
||||
} |
||||
|
||||
w.WriteByte('[') |
||||
|
||||
switch { |
||||
case cfg.Compact: |
||||
// All on one line:
|
||||
for i, v := range l { |
||||
if i > 0 { |
||||
w.WriteByte(',') |
||||
} |
||||
v.WriteTo(w, indent, cfg) |
||||
} |
||||
case cfg.Diffable: |
||||
w.WriteByte('\n') |
||||
inner := indent + " " |
||||
// Each value gets its own line:
|
||||
for _, v := range l { |
||||
w.WriteString(inner) |
||||
v.WriteTo(w, inner, cfg) |
||||
w.WriteString(",\n") |
||||
} |
||||
w.WriteString(indent) |
||||
default: |
||||
inner := indent + " " |
||||
// First and last line shared with bracket:
|
||||
for i, v := range l { |
||||
if i > 0 { |
||||
w.WriteString(",\n") |
||||
w.WriteString(inner) |
||||
} |
||||
v.WriteTo(w, inner, cfg) |
||||
} |
||||
} |
||||
|
||||
w.WriteByte(']') |
||||
} |
||||
@ -0,0 +1,316 @@
|
||||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pretty |
||||
|
||||
import ( |
||||
"bytes" |
||||
"strings" |
||||
"testing" |
||||
) |
||||
|
||||
func TestWriteTo(t *testing.T) { |
||||
tests := []struct { |
||||
desc string |
||||
node node |
||||
|
||||
// All strings have a leading newline trimmed before comparison:
|
||||
normal string |
||||
diffable string |
||||
}{ |
||||
{ |
||||
desc: "string", |
||||
node: stringVal("zaphod"), |
||||
normal: `"zaphod"`, |
||||
diffable: `"zaphod"`, |
||||
}, |
||||
{ |
||||
desc: "raw", |
||||
node: rawVal("42"), |
||||
normal: `42`, |
||||
diffable: `42`, |
||||
}, |
||||
{ |
||||
desc: "keyvals", |
||||
node: keyvals{ |
||||
{"name", stringVal("zaphod")}, |
||||
{"age", rawVal("42")}, |
||||
}, |
||||
normal: ` |
||||
{name: "zaphod", |
||||
age: 42}`, |
||||
diffable: ` |
||||
{ |
||||
name: "zaphod", |
||||
age: 42, |
||||
}`, |
||||
}, |
||||
{ |
||||
desc: "empty list", |
||||
node: list{}, |
||||
normal: ` |
||||
[]`, |
||||
diffable: ` |
||||
[ |
||||
]`, |
||||
}, |
||||
{ |
||||
desc: "empty nested list", |
||||
node: list{list{}}, |
||||
normal: ` |
||||
[[]]`, |
||||
diffable: ` |
||||
[ |
||||
[ |
||||
], |
||||
]`, |
||||
}, |
||||
{ |
||||
desc: "list", |
||||
node: list{ |
||||
stringVal("zaphod"), |
||||
rawVal("42"), |
||||
}, |
||||
normal: ` |
||||
["zaphod", |
||||
42]`, |
||||
diffable: ` |
||||
[ |
||||
"zaphod", |
||||
42, |
||||
]`, |
||||
}, |
||||
{ |
||||
desc: "empty keyvals", |
||||
node: keyvals{}, |
||||
normal: ` |
||||
{}`, |
||||
diffable: ` |
||||
{ |
||||
}`, |
||||
}, |
||||
{ |
||||
desc: "empty nested keyvals", |
||||
node: keyvals{{"k", keyvals{}}}, |
||||
normal: ` |
||||
{k: {}}`, |
||||
diffable: ` |
||||
{ |
||||
k: { |
||||
}, |
||||
}`, |
||||
}, |
||||
{ |
||||
desc: "nested", |
||||
node: list{ |
||||
stringVal("first"), |
||||
list{rawVal("1"), rawVal("2"), rawVal("3")}, |
||||
keyvals{ |
||||
{"trillian", keyvals{ |
||||
{"race", stringVal("human")}, |
||||
{"age", rawVal("36")}, |
||||
}}, |
||||
{"zaphod", keyvals{ |
||||
{"occupation", stringVal("president of the galaxy")}, |
||||
{"features", stringVal("two heads")}, |
||||
}}, |
||||
}, |
||||
keyvals{}, |
||||
}, |
||||
normal: ` |
||||
["first", |
||||
[1, |
||||
2, |
||||
3], |
||||
{trillian: {race: "human", |
||||
age: 36}, |
||||
zaphod: {occupation: "president of the galaxy", |
||||
features: "two heads"}}, |
||||
{}]`, |
||||
diffable: ` |
||||
[ |
||||
"first", |
||||
[ |
||||
1, |
||||
2, |
||||
3, |
||||
], |
||||
{ |
||||
trillian: { |
||||
race: "human", |
||||
age: 36, |
||||
}, |
||||
zaphod: { |
||||
occupation: "president of the galaxy", |
||||
features: "two heads", |
||||
}, |
||||
}, |
||||
{ |
||||
}, |
||||
]`, |
||||
}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
// For readability, we have a newline that won't be there in the output
|
||||
test.normal = strings.TrimPrefix(test.normal, "\n") |
||||
test.diffable = strings.TrimPrefix(test.diffable, "\n") |
||||
|
||||
buf := new(bytes.Buffer) |
||||
test.node.WriteTo(buf, "", &Config{}) |
||||
if got, want := buf.String(), test.normal; got != want { |
||||
t.Errorf("%s: normal rendendered incorrectly\ngot:\n%s\nwant:\n%s", test.desc, got, want) |
||||
} |
||||
buf.Reset() |
||||
test.node.WriteTo(buf, "", &Config{Diffable: true}) |
||||
if got, want := buf.String(), test.diffable; got != want { |
||||
t.Errorf("%s: diffable rendendered incorrectly\ngot:\n%s\nwant:\n%s", test.desc, got, want) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestCompactString(t *testing.T) { |
||||
tests := []struct { |
||||
node |
||||
compact string |
||||
}{ |
||||
{ |
||||
stringVal("abc"), |
||||
"abc", |
||||
}, |
||||
{ |
||||
rawVal("2"), |
||||
"2", |
||||
}, |
||||
{ |
||||
list{ |
||||
rawVal("2"), |
||||
rawVal("3"), |
||||
}, |
||||
"[2,3]", |
||||
}, |
||||
{ |
||||
keyvals{ |
||||
{"name", stringVal("zaphod")}, |
||||
{"age", rawVal("42")}, |
||||
}, |
||||
`{name:"zaphod",age:42}`, |
||||
}, |
||||
{ |
||||
list{ |
||||
list{ |
||||
rawVal("0"), |
||||
rawVal("1"), |
||||
rawVal("2"), |
||||
rawVal("3"), |
||||
}, |
||||
list{ |
||||
rawVal("1"), |
||||
rawVal("2"), |
||||
rawVal("3"), |
||||
rawVal("0"), |
||||
}, |
||||
list{ |
||||
rawVal("2"), |
||||
rawVal("3"), |
||||
rawVal("0"), |
||||
rawVal("1"), |
||||
}, |
||||
}, |
||||
`[[0,1,2,3],[1,2,3,0],[2,3,0,1]]`, |
||||
}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
if got, want := compactString(test.node), test.compact; got != want { |
||||
t.Errorf("%#v: compact = %q, want %q", test.node, got, want) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestShortList(t *testing.T) { |
||||
cfg := &Config{ |
||||
ShortList: 16, |
||||
} |
||||
|
||||
tests := []struct { |
||||
node |
||||
want string |
||||
}{ |
||||
{ |
||||
list{ |
||||
list{ |
||||
rawVal("0"), |
||||
rawVal("1"), |
||||
rawVal("2"), |
||||
rawVal("3"), |
||||
}, |
||||
list{ |
||||
rawVal("1"), |
||||
rawVal("2"), |
||||
rawVal("3"), |
||||
rawVal("0"), |
||||
}, |
||||
list{ |
||||
rawVal("2"), |
||||
rawVal("3"), |
||||
rawVal("0"), |
||||
rawVal("1"), |
||||
}, |
||||
}, |
||||
`[[0,1,2,3], |
||||
[1,2,3,0], |
||||
[2,3,0,1]]`, |
||||
}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
buf := new(bytes.Buffer) |
||||
test.node.WriteTo(buf, "", cfg) |
||||
if got, want := buf.String(), test.want; got != want { |
||||
t.Errorf("%#v: got:\n%s\nwant:\n%s", test.node, got, want) |
||||
} |
||||
} |
||||
} |
||||
|
||||
var benchNode = keyvals{ |
||||
{"list", list{ |
||||
rawVal("0"), |
||||
rawVal("1"), |
||||
rawVal("2"), |
||||
rawVal("3"), |
||||
}}, |
||||
{"keyvals", keyvals{ |
||||
{"a", stringVal("b")}, |
||||
{"c", stringVal("e")}, |
||||
{"d", stringVal("f")}, |
||||
}}, |
||||
} |
||||
|
||||
func benchOpts(b *testing.B, cfg *Config) { |
||||
buf := new(bytes.Buffer) |
||||
benchNode.WriteTo(buf, "", cfg) |
||||
b.SetBytes(int64(buf.Len())) |
||||
b.ResetTimer() |
||||
|
||||
for i := 0; i < b.N; i++ { |
||||
buf.Reset() |
||||
benchNode.WriteTo(buf, "", cfg) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkWriteDefault(b *testing.B) { benchOpts(b, DefaultConfig) } |
||||
func BenchmarkWriteShortList(b *testing.B) { benchOpts(b, &Config{ShortList: 16}) } |
||||
func BenchmarkWriteCompact(b *testing.B) { benchOpts(b, &Config{Compact: true}) } |
||||
func BenchmarkWriteDiffable(b *testing.B) { benchOpts(b, &Config{Diffable: true}) } |
||||
Loading…
Reference in new issue