Merge pull request #3208 from git-lfs/ttaylorr/go-mod

all: use Go modules instead of Glide
This commit is contained in:
Taylor Blau 2018-08-29 16:58:59 -04:00 committed by GitHub
commit 35fe301c44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
600 changed files with 139 additions and 48209 deletions

@ -4,7 +4,7 @@ jobs:
bootstrap:
macos:
xcode: "9.2.0"
working_directory: ~/go/src/github.com/git-lfs/git-lfs
working_directory: ~/src/git-lfs
steps:
- checkout
- run: brew update
@ -14,20 +14,19 @@ jobs:
- run: brew upgrade gettext || brew install gettext
- run:
command: git clone https://github.com/git/git.git git-source
working_directory: ~/go/src/github.com/git-lfs/git-lfs
working_directory: ~/src/git-lfs
- run: echo "export GOPATH=$HOME/go" >> $BASH_ENV
- run: go get -d -v github.com/spf13/cobra
- run: go get -d -v github.com/ThomsonReutersEikon/go-ntlm/ntlm
- save_cache:
key: git-lfs-{{ .Branch }}-{{ .Revision }}
paths:
- /usr/local/Cellar
- ~/src/git-lfs
- ~/go
build_with_system_git:
macos:
xcode: "9.2.0"
working_directory: ~/go/src/github.com/git-lfs/git-lfs
working_directory: ~/src/git-lfs
environment:
GIT_SOURCE_REPO: https://github.com/git/git.git
steps:
@ -42,7 +41,7 @@ jobs:
build_with_earliest_supported_git:
macos:
xcode: "9.2.0"
working_directory: ~/go/src/github.com/git-lfs/git-lfs
working_directory: ~/src/git-lfs
environment:
GIT_EARLIEST_SUPPORTED_VERSION: v2.0.0
GIT_SOURCE_REPO: https://github.com/git/git.git
@ -63,7 +62,7 @@ jobs:
build_with_latest_git:
macos:
xcode: "9.2.0"
working_directory: ~/go/src/github.com/git-lfs/git-lfs
working_directory: ~/src/git-lfs
environment:
GIT_LATEST_SOURCE_BRANCH: master
GIT_SOURCE_REPO: https://github.com/git/git.git

@ -8,6 +8,7 @@ os:
env:
global:
- GIT_LFS_SRC_DIR="$HOME/src/git-lfs"
- GIT_LFS_TEST_DIR="$HOME/git-lfs-tests"
- GIT_SOURCE_REPO="https://github.com/git/git.git"
- GIT_EARLIEST_SUPPORTED_VERSION="v2.0.0
@ -21,6 +22,7 @@ matrix:
os: linux
before_script:
- >
cd "$GIT_LFS_SRC_DIR";
git clone $GIT_SOURCE_REPO git-source;
cd git-source;
git checkout $GIT_LATEST_SOURCE_BRANCH;
@ -31,6 +33,7 @@ matrix:
os: linux
before_script:
- >
cd "$GIT_LFS_SRC_DIR";
git clone $GIT_SOURCE_REPO git-source;
cd git-source;
git checkout $GIT_EARLIEST_SUPPORTED_VERSION;
@ -48,15 +51,9 @@ matrix:
before_install:
- >
repo=`basename $PWD`;
localDir=`dirname $PWD`;
cfDir="`dirname $localDir`/git-lfs";
if [[ "$localDir" != "$cfDir" ]];
then
mv "$localDir" "$cfDir";
cd ../../git-lfs/$repo;
export TRAVIS_BUILD_DIR=`dirname $TRAVIS_BUILD_DIR`/$repo;
fi;
mkdir -p "$(dirname "$GIT_LFS_SRC_DIR")";
mv "$TRAVIS_BUILD_DIR" "$GIT_LFS_SRC_DIR";
export TRAVIS_BUILD_DIR="$GIT_LFS_SRC_DIR";
install: true

@ -117,7 +117,7 @@ tests:
## Updating 3rd party packages
0. Update `glide.yaml`.
0. Update `go.mod`.
0. Run `make vendor` to update the code in the `vendor` directory.
0. Commit the change. Git LFS vendors the full source code in the repository.
0. Submit a pull request.

@ -40,9 +40,6 @@ EXTRA_GC_FLAGS =
# GC_FLAGS are the union of the above two BUILTIN_GC_FLAGS and EXTRA_GC_FLAGS.
GC_FLAGS = $(BUILTIN_GC_FLAGS) $(EXTRA_GC_FLAGS)
# GLIDE is the name of the 'glide' binary used to manage vendored dependencies.
GLIDE ?= glide
# RONN is the name of the 'ronn' program used to generate man pages.
RONN ?= ronn
# RONN_EXTRA_ARGS are extra arguments given to the $(RONN) program when invoked.
@ -205,7 +202,6 @@ resource.syso : \
versioninfo.json script/windows-installer/git-lfs-logo.bmp \
script/windows-installer/git-lfs-logo.ico \
script/windows-installer/git-lfs-wizard-image.bmp
@$(GO) get github.com/josephspurrier/goversioninfo/cmd/goversioninfo
$(GO) generate
# RELEASE_TARGETS is the set of all release artifacts that we generate over a
@ -298,17 +294,16 @@ test : fmt
integration : bin/git-lfs$(X)
make -C t test
# glide.lock is the permanent record of the glide.yaml file, and it is built by
# running 'glide update'.
glide.lock : glide.yaml
$(GLIDE) update
# go.sum is a lockfile based on the contents of go.mod.
go.sum : go.mod
$(GO) mod verify >/dev/null
# vendor updates the glide.lock-file, and installs vendored dependencies into
# vendor updates the go.sum-file, and installs vendored dependencies into
# the vendor/ sub-tree, removing sub-packages (listed below) that are unused by
# Git LFS.
# Git LFS as well as test code.
.PHONY : vendor
vendor : glide.lock
$(GLIDE) install
vendor : go.mod
$(GO) mod vendor -v
$(RM) -r vendor/github.com/ThomsonReutersEikon/go-ntlm/utils
$(RM) -r vendor/github.com/davecgh/go-spew
$(RM) -r vendor/github.com/pmezard/go-difflib
@ -332,9 +327,13 @@ endif
# are vendored in via vendor (see: above).
.PHONY : lint
lint : $(SOURCES)
@$(GO) list -f '{{ join .Deps "\n" }}' . \
| $(XARGS) $(GO) list -f '{{ if not .Standard }}{{ .ImportPath }}{{ end }}' \
| $(GREP) -v "github.com/git-lfs/git-lfs" || exit 0
@! $(GO) list -f '{{ join .Deps "\n" }}' . \
| $(XARGS) $(GO) list -f \
'{{ if and (not .Standard) (not .Module) }} \
{{ .ImportPath }} \
{{ end }}' \
| $(GREP) -v "github.com/git-lfs/git-lfs" \
| $(GREP) "."
# MAN_ROFF_TARGETS is a list of all ROFF-style targets in the man pages.
MAN_ROFF_TARGETS = man/git-lfs-checkout.1 \

@ -8,7 +8,7 @@ environment:
GOPATH: C:\Users\appveyor\go
MSYSTEM: MINGW64
clone_folder: $(GOPATH)\src\github.com\git-lfs\git-lfs
clone_folder: $(USERPROFILE)\src\git-lfs
install:
- rd C:\Go /s /q
@ -17,12 +17,15 @@ install:
- curl -sL -o go1.11.windows-amd64.zip https://storage.googleapis.com/golang/go1.11.windows-amd64.zip
- 7z x go1.11.windows-amd64.zip -oC:\ >nul
- C:\go\bin\go version
- cd %USERPROFILE%\src
- C:\go\bin\go get github.com/josephspurrier/goversioninfo/cmd/goversioninfo
- cd %USERPROFILE%\src\git-lfs
- cinst InnoSetup -y
- cinst strawberryperl -y
- refreshenv
- cinst make
- refreshenv
- set PATH=%GOPATH%\bin\windows_386;%PATH%
- set PATH=%GOPATH%\bin;%PATH%
- ps: |
echo "Go directories in machine PATH environment:"
[environment]::GetEnvironmentVariable("PATH","Machine").split(";") | Select-String -Pattern "\\go\\"

2
debian/rules vendored

@ -1,7 +1,7 @@
#!/usr/bin/make -f
export DH_OPTIONS
export GO15VENDOREXPERIMENT=1
export GO111MODULE=on
#dh_golang doesn't do this for you
ifeq ($(DEB_HOST_ARCH), i386)

56
glide.lock generated

@ -1,56 +0,0 @@
hash: a39eef1817877f30955e685f12d3d610f277c5f6abc0756f6bab6944dc27c4dd
updated: 2018-07-06T12:09:00.254181079-05:00
imports:
- name: github.com/alexbrainman/sspi
version: 4729b3d4d8581b2db83864d1018926e4154f9406
subpackages:
- ntlm
- name: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
- name: github.com/git-lfs/gitobj
version: 0fcb9c3796fa00bd974c86dc25b1919da1479007
subpackages:
- pack
- name: github.com/git-lfs/go-netrc
version: e0e9ca483a183481412e6f5a700ff20a36177503
subpackages:
- netrc
- name: github.com/git-lfs/wildmatch
version: b31c34466d64fcd421b503261f815a303e74296e
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/kr/pty
version: 5cf931ef8f76dccd0910001d74a58a7fca84a83d
- name: github.com/olekukonko/ts
version: ecf753e7c962639ab5a1fb46f7da627d4c0a04b8
- name: github.com/pkg/errors
version: c605e284fe17294bda444b34710735b29d1a9d90
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/rubyist/tracerx
version: 787959303086f44a8c361240dfac53d3e9d53ed2
- name: github.com/spf13/cobra
version: c55cdf33856a08e4822738728b41783292812889
- name: github.com/spf13/pflag
version: 580b9be06c33d8ba9dcc8757ea56b7642472c2f5
- name: github.com/stretchr/testify
version: 6cb3b85ef5a0efef77caef88363ec4d4b5c0976d
subpackages:
- assert
- require
- name: github.com/ThomsonReutersEikon/go-ntlm
version: b00ec39bbdd04f845950f4dbb4fd0a2c3155e830
subpackages:
- ntlm
- ntlm/md4
- name: github.com/xeipuuv/gojsonschema
version: 6b67b3fab74d992bd07f72550006ab2c6907c416
testImports:
- name: github.com/xeipuuv/gojsonpointer
version: 4e3ac2762d5f479393488629ee9370b50873b3a6
- name: github.com/xeipuuv/gojsonreference
version: bd5ef7bd5415a7ac448318e64f11a24cd21e594b

@ -1,36 +0,0 @@
package: github.com/git-lfs/git-lfs
import:
- package: github.com/git-lfs/go-netrc
version: e0e9ca483a183481412e6f5a700ff20a36177503
subpackages:
- netrc
- package: github.com/kr/pty
version: 5cf931ef8f76dccd0910001d74a58a7fca84a83d
- package: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- package: github.com/olekukonko/ts
version: ecf753e7c962639ab5a1fb46f7da627d4c0a04b8
- package: github.com/rubyist/tracerx
version: 787959303086f44a8c361240dfac53d3e9d53ed2
- package: github.com/spf13/cobra
version: c55cdf33856a08e4822738728b41783292812889
- package: github.com/spf13/pflag
version: 580b9be06c33d8ba9dcc8757ea56b7642472c2f5
- package: github.com/stretchr/testify
version: 6cb3b85ef5a0efef77caef88363ec4d4b5c0976d
- package: github.com/ThomsonReutersEikon/go-ntlm
version: b00ec39bbdd04f845950f4dbb4fd0a2c3155e830
subpackages:
- ntlm
- package: github.com/xeipuuv/gojsonschema
version: 6b67b3fab74d992bd07f72550006ab2c6907c416
- package: github.com/pkg/errors
version: c605e284fe17294bda444b34710735b29d1a9d90
- package: github.com/git-lfs/wildmatch
version: b31c34466d64fcd421b503261f815a303e74296e
- package: github.com/alexbrainman/sspi
version: 4729b3d4d8581b2db83864d1018926e4154f9406
subpackages:
- ntlm
- package: github.com/git-lfs/gitobj
version: 0fcb9c3796fa00bd974c86dc25b1919da1479007

22
go.mod Normal file

@ -0,0 +1,22 @@
module github.com/git-lfs/git-lfs
require (
github.com/ThomsonReutersEikon/go-ntlm v0.0.0-20151030004737-b00ec39bbdd0
github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2 // indirect
github.com/git-lfs/gitobj v0.0.0-20180705162808-0fcb9c3796fa
github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18
github.com/git-lfs/wildmatch v0.0.0-20180706170425-b31c34466d64
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/kr/pty v0.0.0-20150511174710-5cf931ef8f76
github.com/olekukonko/ts v0.0.0-20140412220145-ecf753e7c962
github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 // indirect
github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086
github.com/spf13/cobra v0.0.0-20150809222549-c55cdf33856a
github.com/spf13/pflag v0.0.0-20150814225300-580b9be06c33 // indirect
github.com/stretchr/testify v0.0.0-20160504130155-6cb3b85ef5a0
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v0.0.0-20170210233622-6b67b3fab74d
)

42
go.sum Normal file

@ -0,0 +1,42 @@
github.com/ThomsonReutersEikon/go-ntlm v0.0.0-20151030004737-b00ec39bbdd0 h1:iBnLwXNz+Mz2K6MndIM9a4sLBb+TouHB8s0tvx16s4s=
github.com/ThomsonReutersEikon/go-ntlm v0.0.0-20151030004737-b00ec39bbdd0/go.mod h1:0eFwySJYxbNw/r8cJ01IeztpJfwrgrmiLtYig80Yvrc=
github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858 h1:OZQyEhf4BviydsRdmK4ryeJHotDLd7vL1X8+nnxXkfk=
github.com/alexbrainman/sspi v0.0.0-20180125232955-4729b3d4d858/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2 h1:5zdDAMuB3gvbHB1m2BZT9+t9w+xaBmK3ehb7skDXcwM=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/git-lfs/gitobj v0.0.0-20180705162808-0fcb9c3796fa h1:wXVStqoJoNE3tbW33kGIHBRVtUWL7GJxfEmDuBiueFo=
github.com/git-lfs/gitobj v0.0.0-20180705162808-0fcb9c3796fa/go.mod h1:+OqrxLkyEiaSZ7Tz/McVIlPU+p69Dj25MaB3QqV4nN4=
github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18 h1:7Th0eBA4rT8WJNiM1vppjaIv9W5WJinhpbCJvRJxloI=
github.com/git-lfs/go-netrc v0.0.0-20180525200031-e0e9ca483a18/go.mod h1:70O4NAtvWn1jW8V8V+OKrJJYcxDLTmIozfi2fmSz5SI=
github.com/git-lfs/wildmatch v0.0.0-20180706170425-b31c34466d64 h1:ZPW4Gapp3/5fc/LDTp0A/C+s74yZg9jVZBoVan0GSmM=
github.com/git-lfs/wildmatch v0.0.0-20180706170425-b31c34466d64/go.mod h1:SdHAGnApDpnFYQ0vAxbniWR0sn7yLJ3QXo9RRfhn2ew=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/kr/pty v0.0.0-20150511174710-5cf931ef8f76 h1:i5TIRQpbCg4aJMUtVHIhkQnSw++Z405Z5pzqHqeNkdU=
github.com/kr/pty v0.0.0-20150511174710-5cf931ef8f76/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/olekukonko/ts v0.0.0-20140412220145-ecf753e7c962 h1:3ev7csw9n4tLCxb5mtYXooCeQIFmC8Iw+4VhrSBy4zk=
github.com/olekukonko/ts v0.0.0-20140412220145-ecf753e7c962/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17 h1:chPfVn+gpAM5CTpTyVU9j8J+xgRGwmoDlNDLjKnJiYo=
github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 h1:GD+A8+e+wFkqje55/2fOVnZPkoDIu1VooBWfNrnY8Uo=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086 h1:mncRSDOqYCng7jOD+Y6+IivdRI6Kzv2BLWYkWkdQfu0=
github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086/go.mod h1:YpdgDXpumPB/+EGmGTYHeiW/0QVFRzBYTNFaxWfPDk4=
github.com/spf13/cobra v0.0.0-20150809222549-c55cdf33856a h1:pYw8mElqJwrz3T7QVT2EZw1Te6gHW+cOGsuOuRzNdRo=
github.com/spf13/cobra v0.0.0-20150809222549-c55cdf33856a/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v0.0.0-20150814225300-580b9be06c33 h1:JZTN3XaDcPNAKb/i98hWdKD3C0wNQ8ehJRQHcg9eQ10=
github.com/spf13/pflag v0.0.0-20150814225300-580b9be06c33/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/testify v0.0.0-20160504130155-6cb3b85ef5a0 h1:aCHp55cT6UsXkzGy9PoE+pNlDIbLIwcqK35xEnAKWfw=
github.com/stretchr/testify v0.0.0-20160504130155-6cb3b85ef5a0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v0.0.0-20170210233622-6b67b3fab74d h1:BJPiQVOMMtJsJIkrF4T6K3RKbzqr7rkaybMk33dlGUo=
github.com/xeipuuv/gojsonschema v0.0.0-20170210233622-6b67b3fab74d/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

@ -24,7 +24,6 @@ Enterprise.
%prep
%setup -q -n %{name}-%{version}
export GOPATH=`pwd`
mkdir -p src/github.com/git-lfs
ln -s $(pwd) src/github.com/git-lfs/%{name}
@ -57,7 +56,6 @@ git lfs install --system
git lfs uninstall
%check
export GOPATH=`pwd`
export GIT_LFS_TEST_DIR=$(mktemp -d)
# test/git-lfs-server-api/main.go does not compile because github.com/spf13/cobra

@ -6,10 +6,10 @@ if [ "appveyor" = "$USERNAME" ]; then
GOIMPORTS="C:\\Users\\appveyor\\go\\bin\\goimports"
fi
GOCACHE=off make GOIMPORTS="$GOIMPORTS" && make GOIMPORTS="$GOIMPORTS" test
make GOIMPORTS="$GOIMPORTS" && make GOIMPORTS="$GOIMPORTS" test
# re-run test to ensure GIT_TRACE output doesn't leak into the git package
GIT_TRACE=1 GOCACHE=off make GOIMPORTS="$GOIMPORTS" PKGS=git test
GIT_TRACE=1 make GOIMPORTS="$GOIMPORTS" PKGS=git test
pushd t >/dev/null
UNAME=$(uname -s)

@ -1,5 +0,0 @@
<<<<<<< HEAD
pkg/
=======
pkg
>>>>>>> adding License information and go format

@ -1,27 +0,0 @@
Copyright (c) 2013, Thomson Reuters Global Resources
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software
must display the following acknowledgement:
This product includes software developed by the Thomson Reuters Global Resources.
4. Neither the name of the Thomson Reuters Global Resources nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY Thomson Reuters Global Resources ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Thomson Reuters Global Resources BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -1,61 +0,0 @@
# NTLM Implementation for Go
This is a native implementation of NTLM for Go that was implemented using the Microsoft MS-NLMP documentation available at http://msdn.microsoft.com/en-us/library/cc236621.aspx.
The library is currently in use and has been tested with connectionless NTLMv1 and v2 with and without extended session security.
## Usage Notes
Currently the implementation only supports connectionless (datagram) oriented NTLM. We did not need connection oriented NTLM for our usage
and so it is not implemented. However it should be extremely straightforward to implement connection oriented NTLM as all
the operations required are present in the library. The major missing piece is the negotiation of capabilities between
the client and the server, for our use we hardcoded a supported set of negotiation flags.
## Sample Usage as NTLM Client
```go
import "github.com/ThomsonReutersEikon/go-ntlm/ntlm"
session, err = ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionlessMode)
session.SetUserInfo("someuser","somepassword","somedomain")
negotiate := session.GenerateNegotiateMessage()
<send negotiate to server>
challenge, err := ntlm.ParseChallengeMessage(challengeBytes)
session.ProcessChallengeMessage(challenge)
authenticate := session.GenerateAuthenticateMessage()
<send authenticate message to server>
```
## Sample Usage as NTLM Server
```go
session, err := ntlm.CreateServerSession(ntlm.Version1, ntlm.ConnectionlessMode)
session.SetUserInfo("someuser","somepassword","somedomain")
challenge := session.GenerateChallengeMessage()
<send challenge to client>
<receive authentication bytes>
auth, err := ntlm.ParseAuthenticateMessage(authenticateBytes)
session.ProcessAuthenticateMessage(auth)
```
## Generating a message MAC
Once a session is created you can generate the Mac for a message using:
```go
message := "this is some message to sign"
sequenceNumber := 100
signature, err := session.Mac([]byte(message), sequenceNumber)
```
## License
Copyright Thomson Reuters Global Resources 2013
Apache License

@ -1,64 +0,0 @@
//Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information
package ntlm
import (
"bytes"
"encoding/hex"
"testing"
)
func TestMd4(t *testing.T) {
data := []byte{1, 2, 3, 4, 5}
byteData, _ := hex.DecodeString("93ebafdfedd1994e8018cc295cc1a8ee")
if !bytes.Equal(md4(data), byteData) {
t.Error("MD4 result not correct")
}
}
func TestHmacMd5(t *testing.T) {
data := []byte{1, 2, 3, 4, 5}
byteData, _ := hex.DecodeString("9155578efbf3810a2adb4dee232a5fee")
if !bytes.Equal(hmacMd5(data, data), byteData) {
t.Error("HmacMd5 result not correct")
}
}
func TestNonce(t *testing.T) {
data := nonce(10)
if len(data) != 10 {
t.Error("Nonce is incorrect length")
}
}
func TestRc4K(t *testing.T) {
data := []byte{1, 2, 3, 4, 5}
key := []byte{1, 2, 3, 4, 5}
result, err := rc4K(key, data)
if err != nil {
// TODO: Need some sample data to test RC4K
// t.Error("Error returned for RC4K")
}
if !bytes.Equal(result, data) {
// t.Error("RC4K result not correct")
}
}
func TestDesL(t *testing.T) {
key, _ := hex.DecodeString("e52cac67419a9a224a3b108f3fa6cb6d")
message := []byte("12345678")
result, _ := desL(key, message)
expected, _ := hex.DecodeString("1192855D461A9754D189D8AE94D82488E3707C0662C0476A")
if !bytes.Equal(result, expected) {
t.Errorf("DesL did not produce correct result, got %s expected %s", hex.EncodeToString(result), hex.EncodeToString(expected))
}
}
func TestCRC32(t *testing.T) {
bytes := []byte("Discard medicine more than two years old.")
result := crc32(bytes)
expected := uint32(0x6b9cdfe7)
if expected != result {
t.Errorf("CRC 32 data is not correct got %d expected %d", result, expected)
}
}

@ -1,35 +0,0 @@
//Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information
package ntlm
import (
"bytes"
"encoding/hex"
"testing"
)
func TestUTf16ToString(t *testing.T) {
expected, _ := hex.DecodeString("5500730065007200")
result := utf16FromString("User")
if !bytes.Equal(expected, result) {
t.Errorf("UTF16ToString failed got %s expected %s", hex.EncodeToString(result), "5500730065007200")
}
}
func TestMacsEquals(t *testing.T) {
// the MacsEqual should ignore the values in the second 4 bytes
firstSlice := []byte{0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xf0, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}
secondSlice := []byte{0xf1, 0xf2, 0xf3, 0xf4, 0x00, 0x00, 0x00, 0x00, 0xf9, 0xf0, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}
if !MacsEqual(firstSlice, secondSlice) {
t.Errorf("Expected MacsEqual(%v, %v) to be true", firstSlice, secondSlice)
}
}
func TestMacsEqualsFail(t *testing.T) {
// the last bytes in the following test case should cause MacsEqual to return false
firstSlice := []byte{0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xf0, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}
secondSlice := []byte{0xf1, 0xf2, 0xf3, 0xf4, 0x00, 0x00, 0x00, 0x00, 0xf9, 0xf0, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xfe}
if MacsEqual(firstSlice, secondSlice) {
t.Errorf("Expected MacsEqual(%v, %v) to be false", firstSlice, secondSlice)
}
}

@ -1,71 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package md4
import (
"fmt"
"io"
"testing"
)
type md4Test struct {
out string
in string
}
var golden = []md4Test{
{"31d6cfe0d16ae931b73c59d7e0c089c0", ""},
{"bde52cb31de33e46245e05fbdbd6fb24", "a"},
{"ec388dd78999dfc7cf4632465693b6bf", "ab"},
{"a448017aaf21d8525fc10ae87aa6729d", "abc"},
{"41decd8f579255c5200f86a4bb3ba740", "abcd"},
{"9803f4a34e8eb14f96adba49064a0c41", "abcde"},
{"804e7f1c2586e50b49ac65db5b645131", "abcdef"},
{"752f4adfe53d1da0241b5bc216d098fc", "abcdefg"},
{"ad9daf8d49d81988590a6f0e745d15dd", "abcdefgh"},
{"1e4e28b05464316b56402b3815ed2dfd", "abcdefghi"},
{"dc959c6f5d6f9e04e4380777cc964b3d", "abcdefghij"},
{"1b5701e265778898ef7de5623bbe7cc0", "Discard medicine more than two years old."},
{"d7f087e090fe7ad4a01cb59dacc9a572", "He who has a shady past knows that nice guys finish last."},
{"a6f8fd6df617c72837592fc3570595c9", "I wouldn't marry him with a ten foot pole."},
{"c92a84a9526da8abc240c05d6b1a1ce0", "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"},
{"f6013160c4dcb00847069fee3bb09803", "The days of the digital watch are numbered. -Tom Stoppard"},
{"2c3bb64f50b9107ed57640fe94bec09f", "Nepal premier won't resign."},
{"45b7d8a32c7806f2f7f897332774d6e4", "For every action there is an equal and opposite government program."},
{"b5b4f9026b175c62d7654bdc3a1cd438", "His money is twice tainted: 'taint yours and 'taint mine."},
{"caf44e80f2c20ce19b5ba1cab766e7bd", "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"},
{"191fae6707f496aa54a6bce9f2ecf74d", "It's a tiny change to the code and not completely disgusting. - Bob Manchek"},
{"9ddc753e7a4ccee6081cd1b45b23a834", "size: a.out: bad magic"},
{"8d050f55b1cadb9323474564be08a521", "The major problem is with sendmail. -Mark Horton"},
{"ad6e2587f74c3e3cc19146f6127fa2e3", "Give me a rock, paper and scissors and I will move the world. CCFestoon"},
{"1d616d60a5fabe85589c3f1566ca7fca", "If the enemy is within range, then so are you."},
{"aec3326a4f496a2ced65a1963f84577f", "It's well we cannot hear the screams/That we create in others' dreams."},
{"77b4fd762d6b9245e61c50bf6ebf118b", "You remind me of a TV show, but that's all right: I watch it anyway."},
{"e8f48c726bae5e516f6ddb1a4fe62438", "C is as portable as Stonehedge!!"},
{"a3a84366e7219e887423b01f9be7166e", "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"},
{"a6b7aa35157e984ef5d9b7f32e5fbb52", "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"},
{"75661f0545955f8f9abeeb17845f3fd6", "How can you write a big system without C++? -Paul Glick"},
}
func TestGolden(t *testing.T) {
for i := 0; i < len(golden); i++ {
g := golden[i]
c := New()
for j := 0; j < 3; j++ {
if j < 2 {
io.WriteString(c, g.in)
} else {
io.WriteString(c, g.in[0:len(g.in)/2])
c.Sum(nil)
io.WriteString(c, g.in[len(g.in)/2:])
}
s := fmt.Sprintf("%x", c.Sum(nil))
if s != g.out {
t.Fatalf("md4[%d](%s) = %s want %s", j, g.in, s, g.out)
}
c.Reset()
}
}
}

@ -1,102 +0,0 @@
//Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information
package ntlm
import (
"bytes"
"encoding/base64"
"encoding/hex"
"testing"
)
func checkPayloadStruct(t *testing.T, payloadStruct *PayloadStruct, len uint16, offset uint32) {
if payloadStruct.Len != len || payloadStruct.Offset != offset {
t.Errorf("Failed to parse payload struct %d, %d", payloadStruct.Len, payloadStruct.Offset)
}
}
func TestParseNTLMv1AsV2(t *testing.T) {
ntlmv1data := "TlRMTVNTUAADAAAAGAAYALYAAAAYABgAzgAAADQANABIAAAAIAAgAHwAAAAaABoAnAAAABAAEADmAAAAVYKQQgUCzg4AAAAPYQByAHIAYQB5ADEAMgAuAG0AcwBnAHQAcwB0AC4AcgBlAHUAdABlAHIAcwAuAGMAbwBtAHUAcwBlAHIAcwB0AHIAZQBzAHMAMQAwADAAMAAwADgATgBZAEMAVgBBADEAMgBTADIAQwBNAFMAQQDguXWdC2hLH+C5dZ0LaEsf4Ll1nQtoSx9nI+fkE73qtElnkDiSQbxfcDN9zbtO1qfyK3ZTI6CUhvjxmXnpZEjY"
authBytes, err := base64.StdEncoding.DecodeString(ntlmv1data)
_, err = ParseAuthenticateMessage(authBytes, 2)
if err == nil {
t.Error("Should have returned error when tring to parse an NTLMv1 authenticate message as NTLMv2")
}
_, err = ParseAuthenticateMessage(authBytes, 1)
if err != nil {
t.Error("Should not have returned error when tring to parse an NTLMv1 authenticate message")
}
}
func TestAuthenticateNtlmV1(t *testing.T) {
authenticateMessage := "TlRMTVNTUAADAAAAGAAYAIgAAAAYABgAoAAAAAAAAABYAAAAIAAgAFgAAAAQABAAeAAAABAAEAC4AAAAVYKQYgYBsR0AAAAP2BgW++b14Dh6Z5B4Xs1DiHAAYQB1AGwAQABwAGEAdQBsAGQAaQB4AC4AbgBlAHQAVwBJAE4ANwBfAEkARQA4ACugxZFzvHB4P6LdKbbZpiYHo2ErZURLiSugxZFzvHB4P6LdKbbZpiYHo2ErZURLibmpCUlnbq2I4LAdEhLdg7I="
authenticateData, err := base64.StdEncoding.DecodeString(authenticateMessage)
if err != nil {
t.Error("Could not base64 decode message data")
}
a, err := ParseAuthenticateMessage(authenticateData, 1)
if err != nil {
t.Error("Could not parse authenticate message")
}
a.String()
outBytes := a.Bytes()
if len(outBytes) > 0 {
reparsed, err := ParseAuthenticateMessage(outBytes, 1)
if err != nil {
t.Error("Could not re-parse authenticate message")
}
if reparsed.String() != a.String() {
t.Error("Reparsed message is not the same")
}
} else {
t.Error("Invalid authenticate messsage bytes")
}
}
func TestAuthenticateNtlmV2(t *testing.T) {
authenticateMessage := "TlRMTVNTUAADAAAAGAAYAI4AAAAGAQYBpgAAAAAAAABYAAAAIAAgAFgAAAAWABYAeAAAABAAEACsAQAAVYKQQgYAchcAAAAPpdhi9ItaLWwSGpFMT4VQbnAAYQB1AGwAQABwAGEAdQBsAGQAaQB4AC4AbgBlAHQASQBQAC0AMABBADAAQwAzAEEAMQBFAAE/QEbbIB1InAX5KMgp4s4wmpPZ9jp9T3EC95rRY01DhMSv1kei5wYBAQAAAAAAADM6xfahoM0BMJqT2fY6fU8AAAAAAgAOAFIARQBVAFQARQBSAFMAAQAcAFUASwBCAFAALQBDAEIAVABSAE0ARgBFADAANgAEABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAMANAB1AGsAYgBwAC0AYwBiAHQAcgBtAGYAZQAwADYALgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQABQAWAFIAZQB1AHQAZQByAHMALgBuAGUAdAAIADAAMAAAAAAAAAAAAAAAADAAAFaspfI82pMCKSuN2L09orn37EQVvxCSqVqQhCloFhQeAAAAAAAAAADRgm1iKYwwmIF3axms/dIe"
authenticateData, err := base64.StdEncoding.DecodeString(authenticateMessage)
if err != nil {
t.Error("Could not base64 decode message data")
}
a, err := ParseAuthenticateMessage(authenticateData, 2)
if err != nil || a == nil {
t.Error("Failed to parse authenticate message " + err.Error())
}
checkPayloadStruct(t, a.LmChallengeResponse, 24, 142)
checkPayloadStruct(t, a.NtChallengeResponseFields, 262, 166)
checkPayloadStruct(t, a.DomainName, 0, 88)
checkPayloadStruct(t, a.UserName, 32, 88)
checkPayloadStruct(t, a.Workstation, 22, 120)
checkPayloadStruct(t, a.EncryptedRandomSessionKey, 16, 428)
if a.NegotiateFlags != uint32(1116766805) {
t.Errorf("Authenticate negotiate flags not correct should be %d got %d", uint32(1116766805), a.NegotiateFlags)
}
mic, err := hex.DecodeString("a5d862f48b5a2d6c121a914c4f85506e")
if !bytes.Equal(a.Mic, mic) {
t.Errorf("Mic not correct, should be %s, got %s", "a5d862f48b5a2d6c121a914c4f85506e", hex.EncodeToString(a.Mic))
}
if len(a.Payload) != 356 {
t.Errorf("Length of payload is incorrect got: %d, should be %d", len(a.Payload), 356)
}
a.String()
// Generate the bytes from the message and reparse it and make sure that works
bytes := a.Bytes()
if len(bytes) == 0 {
}
}

@ -1,65 +0,0 @@
//Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information
package ntlm
import (
"bytes"
"encoding/base64"
"encoding/hex"
"fmt"
"testing"
)
func TestDecodeChallenge(t *testing.T) {
challengeMessage := "TlRMTVNTUAACAAAAAAAAADgAAADzgpjiuaopAbx9ejQAAAAAAAAAAKIAogA4AAAABQLODgAAAA8CAA4AUgBFAFUAVABFAFIAUwABABwAVQBLAEIAUAAtAEMAQgBUAFIATQBGAEUAMAA2AAQAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAwA0AHUAawBiAHAALQBjAGIAdAByAG0AZgBlADAANgAuAFIAZQB1AHQAZQByAHMALgBuAGUAdAAFABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAAAAAA="
challengeData, err := base64.StdEncoding.DecodeString(challengeMessage)
if err != nil {
t.Error("Could not base64 decode message data")
}
challenge, err := ParseChallengeMessage(challengeData)
if err != nil || challenge == nil {
t.Error("Failed to parse challenge message " + err.Error())
}
if challenge.TargetName.Len != 0 || challenge.TargetName.MaxLen != 0 || challenge.TargetName.Offset != 56 {
values := fmt.Sprintf("TargetName Len:%v MaxLen:%v Offset:%v", challenge.TargetName.Len, challenge.TargetName.MaxLen, challenge.TargetName.Offset)
t.Error("Failed to parse Target Name in challenge: " + values)
}
if challenge.NegotiateFlags != uint32(3801645811) {
t.Errorf("Challenge negotiate flags not correct should be %v got %d", uint32(3801645811), challenge.NegotiateFlags)
}
serverChallenge, err := hex.DecodeString("B9AA2901BC7D7A34")
if !bytes.Equal(challenge.ServerChallenge, serverChallenge) {
hex := hex.EncodeToString(challenge.ServerChallenge)
t.Error("Server challenge is not correct '" + hex + "'")
}
if challenge.Version.ProductMajorVersion != 5 || challenge.Version.ProductMinorVersion != 2 || challenge.Version.ProductBuild != 3790 || challenge.Version.NTLMRevisionCurrent != 15 {
t.Error("Version information is not correct: '" + challenge.Version.String() + "'")
}
if len(challenge.Payload) != int(challenge.TargetInfoPayloadStruct.Len) {
t.Error("Payload length is not long enough")
}
challenge.String()
outBytes := challenge.Bytes()
if len(outBytes) > 0 {
reparsed, err := ParseChallengeMessage(outBytes)
if err != nil {
t.Error("Could not re-parse challenge message")
}
if reparsed.String() != challenge.String() {
t.Error("Reparsed message is not the same")
}
} else {
t.Error("Invalid challenge messsage bytes")
}
}

@ -1,31 +0,0 @@
//Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information
package ntlm
import (
"encoding/binary"
"encoding/hex"
"testing"
)
func TestFlags(t *testing.T) {
// Sample value from 4.2.2 NTLM v1 Authentication
bytes, _ := hex.DecodeString("338202e2")
flags := uint32(0)
flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags)
flags = NTLMSSP_NEGOTIATE_56.Set(flags)
flags = NTLMSSP_NEGOTIATE_128.Set(flags)
flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags)
flags = NTLMSSP_TARGET_TYPE_SERVER.Set(flags)
flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags)
flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags)
flags = NTLMSSP_NEGOTIATE_SEAL.Set(flags)
flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags)
flags = NTLM_NEGOTIATE_OEM.Set(flags)
flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags)
if flags != binary.LittleEndian.Uint32(bytes) {
t.Error("NTLM Flags are not correct")
}
}

@ -1,238 +0,0 @@
//Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information
package ntlm
import (
"bytes"
"encoding/base64"
"encoding/hex"
"testing"
)
func TestLMOWFv1(t *testing.T) {
// Sample from MS-NLMP
result, err := lmowfv1("Password")
expected, _ := hex.DecodeString("e52cac67419a9a224a3b108f3fa6cb6d")
if err != nil || !bytes.Equal(result, expected) {
t.Errorf("LMNOWFv1 is not correct, got %s expected %s", hex.EncodeToString(result), "e52cac67419a9a224a3b108f3fa6cb6d")
}
}
func TestNTOWFv1(t *testing.T) {
// Sample from MS-NLMP
result := ntowfv1("Password")
expected, _ := hex.DecodeString("a4f49c406510bdcab6824ee7c30fd852")
if !bytes.Equal(result, expected) {
t.Error("NTOWFv1 is not correct")
}
}
func checkV1Value(t *testing.T, name string, value []byte, expected string, err error) {
if err != nil {
t.Errorf("NTLMv1 %s received error: %s", name, err)
} else {
expectedBytes, _ := hex.DecodeString(expected)
if !bytes.Equal(expectedBytes, value) {
t.Errorf("NTLMv1 %s is not correct got %s expected %s", name, hex.EncodeToString(value), expected)
}
}
}
// There was an issue where all NTLMv1 authentications with extended session security
// would authenticate. This was due to a bug in the MS-NLMP docs. This tests for that issue
func TestNtlmV1ExtendedSessionSecurity(t *testing.T) {
// NTLMv1 with extended session security
challengeMessage := "TlRMTVNTUAACAAAAAAAAADgAAABVgphiRy3oSZvn1I4AAAAAAAAAAKIAogA4AAAABQEoCgAAAA8CAA4AUgBFAFUAVABFAFIAUwABABwAVQBLAEIAUAAtAEMAQgBUAFIATQBGAEUAMAA2AAQAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAwA0AHUAawBiAHAALQBjAGIAdAByAG0AZgBlADAANgAuAFIAZQB1AHQAZQByAHMALgBuAGUAdAAFABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAAAAAA="
authenticateMessage := "TlRMTVNTUAADAAAAGAAYAJgAAAAYABgAsAAAAAAAAABIAAAAOgA6AEgAAAAWABYAggAAABAAEADIAAAAVYKYYgUCzg4AAAAPMQAwADAAMAAwADEALgB3AGMAcABAAHQAaABvAG0AcwBvAG4AcgBlAHUAdABlAHIAcwAuAGMAbwBtAE4AWQBDAFMATQBTAEcAOQA5ADAAOQBRWAK3h/TIywAAAAAAAAAAAAAAAAAAAAA3tp89kZU1hs1XZp7KTyGm3XsFAT9stEDW9YXDaeYVBmBcBb//2FOu"
challengeData, _ := base64.StdEncoding.DecodeString(challengeMessage)
c, _ := ParseChallengeMessage(challengeData)
authenticateData, _ := base64.StdEncoding.DecodeString(authenticateMessage)
msg, err := ParseAuthenticateMessage(authenticateData, 1)
if err != nil {
t.Errorf("Could not process authenticate message: %s", err)
}
context, err := CreateServerSession(Version1, ConnectionlessMode)
if err != nil {
t.Errorf("Could not create NTLMv1 session")
}
context.SetUserInfo("100001.wcp.thomsonreuters.com", "notmypass", "")
context.SetServerChallenge(c.ServerChallenge)
err = context.ProcessAuthenticateMessage(msg)
if err == nil {
t.Errorf("This message should have failed to authenticate, but it passed", err)
}
}
func TestNtlmV1(t *testing.T) {
flags := uint32(0)
flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags)
flags = NTLMSSP_NEGOTIATE_56.Set(flags)
flags = NTLMSSP_NEGOTIATE_128.Set(flags)
flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags)
flags = NTLMSSP_TARGET_TYPE_SERVER.Set(flags)
flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags)
flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags)
flags = NTLMSSP_NEGOTIATE_SEAL.Set(flags)
flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags)
flags = NTLM_NEGOTIATE_OEM.Set(flags)
flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags)
n := new(V1ClientSession)
n.SetUserInfo("User", "Password", "Domain")
n.NegotiateFlags = flags
n.responseKeyNT, _ = hex.DecodeString("a4f49c406510bdcab6824ee7c30fd852")
n.responseKeyLM, _ = hex.DecodeString("e52cac67419a9a224a3b108f3fa6cb6d")
n.clientChallenge, _ = hex.DecodeString("aaaaaaaaaaaaaaaa")
n.serverChallenge, _ = hex.DecodeString("0123456789abcdef")
var err error
// 4.2.2.1.3 Session Base Key and Key Exchange Key
err = n.computeSessionBaseKey()
checkV1Value(t, "sessionBaseKey", n.sessionBaseKey, "d87262b0cde4b1cb7499becccdf10784", err)
err = n.computeKeyExchangeKey()
checkV1Value(t, "keyExchangeKey", n.keyExchangeKey, "d87262b0cde4b1cb7499becccdf10784", err)
// 4.2.2.2.1 NTLMv1 Response
// NTChallengeResponse with With NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY not set
err = n.computeExpectedResponses()
checkV1Value(t, "NTChallengeResponse", n.ntChallengeResponse, "67c43011f30298a2ad35ece64f16331c44bdbed927841f94", err)
// 4.2.2.2.2 LMv1 Response
// The LmChallengeResponse is specified in section 3.3.1. With the NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag
// not set and with the NoLMResponseNTLMv1 flag not set
checkV1Value(t, "LMChallengeResponse", n.lmChallengeResponse, "98def7b87f88aa5dafe2df779688a172def11c7d5ccdef13", err)
// If the NTLMSSP_NEGOTIATE_LM_KEY flag is set then the KeyExchangeKey is:
n.NegotiateFlags = NTLMSSP_NEGOTIATE_LM_KEY.Set(n.NegotiateFlags)
err = n.computeKeyExchangeKey()
checkV1Value(t, "keyExchangeKey with NTLMSSP_NEGOTIATE_LM_KEY", n.keyExchangeKey, "b09e379f7fbecb1eaf0afdcb0383c8a0", err)
n.NegotiateFlags = NTLMSSP_NEGOTIATE_LM_KEY.Unset(n.NegotiateFlags)
// 4.2.2.2.3 Encrypted Session Key
//n.randomSessionKey, _ = hex.DecodeString("55555555555555555555555555555555")
// RC4 decryption of the EncryptedRandomSessionKey with the KeyExchange key
//err = n.computeKeyExchangeKey()
//n.encryptedRandomSessionKey, err = hex.DecodeString("518822b1b3f350c8958682ecbb3e3cb7")
//err = n.computeExportedSessionKey()
//checkV1Value(t, "ExportedSessionKey", n.exportedSessionKey, "55555555555555555555555555555555", err)
// NTLMSSP_REQUEST_NON_NT_SESSION_KEY is set:
n.NegotiateFlags = NTLMSSP_REQUEST_NON_NT_SESSION_KEY.Set(n.NegotiateFlags)
err = n.computeKeyExchangeKey()
// n.encryptedRandomSessionKey, err = hex.DecodeString("7452ca55c225a1ca04b48fae32cf56fc")
// err = n.computeExportedSessionKey()
// checkV1Value(t, "ExportedSessionKey - NTLMSSP_REQUEST_NON_NT_SESSION_KEY", n.exportedSessionKey, "55555555555555555555555555555555", err)
n.NegotiateFlags = NTLMSSP_REQUEST_NON_NT_SESSION_KEY.Unset(n.NegotiateFlags)
// NTLMSSP_NEGOTIATE_LM_KEY is set:
n.NegotiateFlags = NTLMSSP_NEGOTIATE_LM_KEY.Set(n.NegotiateFlags)
err = n.computeKeyExchangeKey()
// n.encryptedRandomSessionKey, err = hex.DecodeString("4cd7bb57d697ef9b549f02b8f9b37864")
// err = n.computeExportedSessionKey()
// checkV1Value(t, "ExportedSessionKey - NTLMSSP_NEGOTIATE_LM_KEY", n.exportedSessionKey, "55555555555555555555555555555555", err)
n.NegotiateFlags = NTLMSSP_NEGOTIATE_LM_KEY.Unset(n.NegotiateFlags)
// 4.2.2.3 Messages
challengeMessageBytes, _ := hex.DecodeString("4e544c4d53535000020000000c000c003800000033820a820123456789abcdef00000000000000000000000000000000060070170000000f530065007200760065007200")
challengeMessage, err := ParseChallengeMessage(challengeMessageBytes)
if err == nil {
challengeMessage.String()
} else {
t.Errorf("Could not parse challenge message: %s", err)
}
client := new(V1ClientSession)
client.SetUserInfo("User", "Password", "Domain")
err = client.ProcessChallengeMessage(challengeMessage)
if err != nil {
t.Errorf("Could not process challenge message: %s", err)
}
server := new(V1ServerSession)
server.SetUserInfo("User", "Password", "Domain")
authenticateMessageBytes, err := hex.DecodeString("4e544c4d5353500003000000180018006c00000018001800840000000c000c00480000000800080054000000100010005c000000100010009c000000358280e20501280a0000000f44006f006d00610069006e00550073006500720043004f004d005000550054004500520098def7b87f88aa5dafe2df779688a172def11c7d5ccdef1367c43011f30298a2ad35ece64f16331c44bdbed927841f94518822b1b3f350c8958682ecbb3e3cb7")
authenticateMessage, err := ParseAuthenticateMessage(authenticateMessageBytes, 1)
if err == nil {
authenticateMessage.String()
} else {
t.Errorf("Could not parse authenticate message: %s", err)
}
server = new(V1ServerSession)
server.SetUserInfo("User", "Password", "Domain")
server.serverChallenge = challengeMessage.ServerChallenge
err = server.ProcessAuthenticateMessage(authenticateMessage)
if err != nil {
t.Errorf("Could not process authenticate message: %s", err)
}
}
func TestNTLMv1WithClientChallenge(t *testing.T) {
flags := uint32(0)
flags = NTLMSSP_NEGOTIATE_56.Set(flags)
flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags)
flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags)
flags = NTLMSSP_TARGET_TYPE_SERVER.Set(flags)
flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags)
flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags)
flags = NTLMSSP_NEGOTIATE_SEAL.Set(flags)
flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags)
flags = NTLM_NEGOTIATE_OEM.Set(flags)
flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags)
n := new(V1Session)
n.NegotiateFlags = flags
n.responseKeyNT, _ = hex.DecodeString("a4f49c406510bdcab6824ee7c30fd852")
n.responseKeyLM, _ = hex.DecodeString("e52cac67419a9a224a3b108f3fa6cb6d")
n.clientChallenge, _ = hex.DecodeString("aaaaaaaaaaaaaaaa")
n.serverChallenge, _ = hex.DecodeString("0123456789abcdef")
var err error
// 4.2.2.1.3 Session Base Key and Key Exchange Key
err = n.computeExpectedResponses()
err = n.computeSessionBaseKey()
checkV1Value(t, "sessionBaseKey", n.sessionBaseKey, "d87262b0cde4b1cb7499becccdf10784", err)
checkV1Value(t, "LMv1Response", n.lmChallengeResponse, "aaaaaaaaaaaaaaaa00000000000000000000000000000000", err)
checkV1Value(t, "NTLMv1Response", n.ntChallengeResponse, "7537f803ae367128ca458204bde7caf81e97ed2683267232", err)
err = n.computeKeyExchangeKey()
checkV1Value(t, "keyExchangeKey", n.keyExchangeKey, "eb93429a8bd952f8b89c55b87f475edc", err)
challengeMessageBytes, _ := hex.DecodeString("4e544c4d53535000020000000c000c003800000033820a820123456789abcdef00000000000000000000000000000000060070170000000f530065007200760065007200")
challengeMessage, err := ParseChallengeMessage(challengeMessageBytes)
if err == nil {
challengeMessage.String()
} else {
t.Errorf("Could not parse challenge message: %s", err)
}
client := new(V1ClientSession)
client.SetUserInfo("User", "Password", "Domain")
err = client.ProcessChallengeMessage(challengeMessage)
if err != nil {
t.Errorf("Could not process challenge message: %s", err)
}
server := new(V1ServerSession)
server.SetUserInfo("User", "Password", "Domain")
server.serverChallenge = challengeMessage.ServerChallenge
authenticateMessageBytes, _ := hex.DecodeString("4e544c4d5353500003000000180018006c00000018001800840000000c000c00480000000800080054000000100010005c000000000000009c000000358208820501280a0000000f44006f006d00610069006e00550073006500720043004f004d0050005500540045005200aaaaaaaaaaaaaaaa000000000000000000000000000000007537f803ae367128ca458204bde7caf81e97ed2683267232")
authenticateMessage, err := ParseAuthenticateMessage(authenticateMessageBytes, 1)
if err == nil {
authenticateMessage.String()
} else {
t.Errorf("Could not parse authenticate message: %s", err)
}
err = server.ProcessAuthenticateMessage(authenticateMessage)
if err != nil {
t.Errorf("Could not process authenticate message: %s", err)
}
checkV1Value(t, "SealKey", server.ClientSealingKey, "04dd7f014d8504d265a25cc86a3a7c06", nil)
checkV1Value(t, "SignKey", server.ClientSigningKey, "60e799be5c72fc92922ae8ebe961fb8d", nil)
}

@ -1,189 +0,0 @@
//Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information
package ntlm
import (
"bytes"
"encoding/base64"
"encoding/hex"
"strings"
"testing"
"time"
)
func checkV2Value(t *testing.T, name string, value []byte, expected string, err error) {
if err != nil {
t.Errorf("NTLMv2 %s received error: %s", name, err)
} else {
expectedBytes, _ := hex.DecodeString(expected)
if !bytes.Equal(expectedBytes, value) {
t.Errorf("NTLMv2 %s is not correct got %s expected %s", name, hex.EncodeToString(value), expected)
}
}
}
func TestNTOWFv2(t *testing.T) {
result := ntowfv2("User", "Password", "Domain")
// Sample value from 4.2.4.1.1 in MS-NLMP
expected, _ := hex.DecodeString("0c868a403bfd7a93a3001ef22ef02e3f")
if !bytes.Equal(result, expected) {
t.Errorf("NTOWFv2 is not correct got %s expected %s", hex.EncodeToString(result), "0c868a403bfd7a93a3001ef22ef02e3f")
}
}
func TestNTLMv2(t *testing.T) {
flags := uint32(0)
flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags)
flags = NTLMSSP_NEGOTIATE_56.Set(flags)
flags = NTLMSSP_NEGOTIATE_128.Set(flags)
flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags)
flags = NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags)
flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags)
flags = NTLMSSP_TARGET_TYPE_SERVER.Set(flags)
flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags)
flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags)
flags = NTLMSSP_NEGOTIATE_SEAL.Set(flags)
flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags)
flags = NTLM_NEGOTIATE_OEM.Set(flags)
flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags)
// n := new(V2Session)
// n.SetUserInfo("User","Password","Domain")
// n.NegotiateFlags = flags
// n.responseKeyNT, _ = hex.DecodeString("0c868a403bfd7a93a3001ef22ef02e3f")
// n.responseKeyLM = n.responseKeyNT
// n.clientChallenge, _ = hex.DecodeString("aaaaaaaaaaaaaaaa")
// n.serverChallenge, _ = hex.DecodeString("0123456789abcdef")
// Encrypted Random Session key
//c5 da d2 54 4f c9 79 90 94 ce 1c e9 0b c9 d0 3e
// Challenge message
client := new(V2ClientSession)
client.SetUserInfo("User", "Password", "Domain")
challengeMessageBytes, _ := hex.DecodeString("4e544c4d53535000020000000c000c003800000033828ae20123456789abcdef00000000000000002400240044000000060070170000000f53006500720076006500720002000c0044006f006d00610069006e0001000c0053006500720076006500720000000000")
challengeMessage, err := ParseChallengeMessage(challengeMessageBytes)
if err == nil {
challengeMessage.String()
} else {
t.Errorf("Could not parse challenge message: %s", err)
}
err = client.ProcessChallengeMessage(challengeMessage)
if err != nil {
t.Errorf("Could not process challenge message: %s", err)
}
server := new(V2ServerSession)
server.SetUserInfo("User", "Password", "Domain")
server.serverChallenge = challengeMessage.ServerChallenge
// Authenticate message
r := strings.NewReplacer("\n", "", "\t", "", " ", "")
authenticateMessageBytes, _ := hex.DecodeString(r.Replace(`
4e544c4d535350000300000018001800
6c00000054005400840000000c000c00
48000000080008005400000010001000
5c00000010001000d8000000358288e2
0501280a0000000f44006f006d006100
69006e00550073006500720043004f00
4d005000550054004500520086c35097
ac9cec102554764a57cccc19aaaaaaaa
aaaaaaaa68cd0ab851e51c96aabc927b
ebef6a1c010100000000000000000000
00000000aaaaaaaaaaaaaaaa00000000
02000c0044006f006d00610069006e00
01000c00530065007200760065007200
0000000000000000c5dad2544fc97990
94ce1ce90bc9d03e`))
authenticateMessage, err := ParseAuthenticateMessage(authenticateMessageBytes, 2)
if err == nil {
authenticateMessage.String()
} else {
t.Errorf("Could not parse authenticate message: %s", err)
}
err = server.ProcessAuthenticateMessage(authenticateMessage)
if err != nil {
t.Errorf("Could not process authenticate message: %s", err)
}
checkV2Value(t, "SessionBaseKey", server.sessionBaseKey, "8de40ccadbc14a82f15cb0ad0de95ca3", nil)
checkV2Value(t, "NTChallengeResponse", server.ntChallengeResponse[0:16], "68cd0ab851e51c96aabc927bebef6a1c", nil)
checkV2Value(t, "LMChallengeResponse", server.lmChallengeResponse, "86c35097ac9cec102554764a57cccc19aaaaaaaaaaaaaaaa", nil)
checkV2Value(t, "client seal key", server.ClientSealingKey, "59f600973cc4960a25480a7c196e4c58", nil)
checkV2Value(t, "client signing key", server.ClientSigningKey, "4788dc861b4782f35d43fd98fe1a2d39", nil)
// Have the server generate an initial challenge message
challenge, err := server.GenerateChallengeMessage()
challenge.String()
// Have the client process this server challenge message
client = new(V2ClientSession)
client.SetUserInfo("User", "Password", "Domain")
err = client.ProcessChallengeMessage(challenge)
if err != nil {
t.Errorf("Could not process server generated challenge message: %s", err)
}
// TODO: we should be able to use the ntlm library end to end to make sure
// that Mac, VerifyMac
// // the client should be able to verify the server's mac
// sig := "<NTLM><foo><bar>"
// mac, err := server.Mac([]byte(sig), 100)
// if err != nil {
// t.Errorf("Could not generate a mac for %s", sig)
// }
// matches, err := client.VerifyMac([]byte(sig), mac, 100)
// if err != nil {
// t.Errorf("Could not verify mac for %s (mac = %v)", sig, mac)
// }
// if !matches {
// t.Errorf("Server's Mac couldn't be verified by client")
// }
// mac, err = client.Mac([]byte(sig), 100)
// if err != nil {
// t.Errorf("Could not generate a mac for %s", sig)
// }
// matches, err = server.VerifyMac([]byte(sig), mac, 100)
// if err != nil {
// t.Errorf("Could not verify mac for %s (mac = %v)", sig, mac)
// }
// if !matches {
// t.Errorf("Client's Mac couldn't be verified by server")
// }
}
func TestNTLMv2WithDomain(t *testing.T) {
authenticateMessage := "TlRMTVNTUAADAAAAGAAYALYAAADSANIAzgAAADQANABIAAAAIAAgAHwAAAAaABoAnAAAABAAEACgAQAAVYKQQgUCzg4AAAAPYQByAHIAYQB5ADEAMgAuAG0AcwBnAHQAcwB0AC4AcgBlAHUAdABlAHIAcwAuAGMAbwBtAHUAcwBlAHIAcwB0AHIAZQBzAHMAMQAwADAAMAAwADgATgBZAEMAVgBBADEAMgBTADIAQwBNAFMAQQBPYrLjU4h0YlWZeEoNvTJtBQMnnJuAeUwsP+vGmAHNRBpgZ+4ChQLqAQEAAAAAAACPFEIFjx7OAQUDJ5ybgHlMAAAAAAIADgBSAEUAVQBUAEUAUgBTAAEAHABVAEsAQgBQAC0AQwBCAFQAUgBNAEYARQAwADYABAAWAFIAZQB1AHQAZQByAHMALgBuAGUAdAADADQAdQBrAGIAcAAtAGMAYgB0AHIAbQBmAGUAMAA2AC4AUgBlAHUAdABlAHIAcwAuAG4AZQB0AAUAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAAAAAAAAAAANuvnqD3K88ZpjkLleL0NW"
server := new(V2ServerSession)
server.SetUserInfo("blahblah", "Welcome1", "blahblah")
authenticateData, _ := base64.StdEncoding.DecodeString(authenticateMessage)
a, _ := ParseAuthenticateMessage(authenticateData, 2)
serverChallenge, _ := hex.DecodeString("3d74b2d04ebe1eb3")
server.SetServerChallenge(serverChallenge)
err := server.ProcessAuthenticateMessage(a)
if err != nil {
t.Error("Could not process authenticate message: %s\n", err)
}
}
func TestWindowsTimeConversion(t *testing.T) {
// From http://davenport.sourceforge.net/ntlm.html#theType3Message
// Next, the blob is constructed. The timestamp is the most tedious part of this; looking at the clock on my desk,
// it's about 6:00 AM EDT on June 17th, 2003. In Unix time, that would be 1055844000 seconds after the Epoch.
// Adding 11644473600 will give us seconds after January 1, 1601 (12700317600). Multiplying by 107 (10000000)
// will give us tenths of a microsecond (127003176000000000). As a little-endian 64-bit value, this is
// "0x0090d336b734c301" (in hexadecimal).
unix := time.Unix(1055844000, 0)
result := timeToWindowsFileTime(unix)
checkV2Value(t, "Timestamp", result, "0090d336b734c301", nil)
}

@ -1,66 +0,0 @@
//Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information
package ntlm
import (
"bytes"
"encoding/hex"
"testing"
)
func checkSigValue(t *testing.T, name string, value []byte, expected string, err error) {
if err != nil {
t.Errorf("Signature %s received error: %s", name, err)
} else {
expectedBytes, _ := hex.DecodeString(expected)
if !bytes.Equal(expectedBytes, value) {
t.Errorf("Signature %s is not correct got %s expected %s", name, hex.EncodeToString(value), expected)
}
}
}
// 4.2.2.4 GSS_WrapEx Examples
func TestSealWithoutExtendedSessionSecurity(t *testing.T) {
key, _ := hex.DecodeString("55555555555555555555555555555555")
handle, _ := rc4Init(key)
plaintext, _ := hex.DecodeString("50006c00610069006e007400650078007400")
seqNum := uint32(0)
flags := uint32(0)
sealed, sig := seal(flags, handle, nil, seqNum, plaintext)
checkSigValue(t, "Sealed message", sealed, "56fe04d861f9319af0d7238a2e3b4d457fb8", nil)
checkSigValue(t, "Randompad", sig.RandomPad, "00000000", nil)
checkSigValue(t, "RC4 Checksum", sig.CheckSum, "09dcd1df", nil)
checkSigValue(t, "Xor Seq", sig.SeqNum, "2e459d36", nil)
}
func TestSealSignWithExtendedSessionSecurity(t *testing.T) {
sealKey, _ := hex.DecodeString("04dd7f014d8504d265a25cc86a3a7c06")
signKey, _ := hex.DecodeString("60e799be5c72fc92922ae8ebe961fb8d")
handle, _ := rc4Init(sealKey)
plaintext, _ := hex.DecodeString("50006c00610069006e007400650078007400")
seqNum := uint32(0)
flags := uint32(0)
flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags)
sealed, sig := seal(flags, handle, signKey, seqNum, plaintext)
checkSigValue(t, "Sealed Data", sealed, "a02372f6530273f3aa1eb90190ce5200c99d", nil)
checkSigValue(t, "CheckSum", sig.CheckSum, "ff2aeb52f681793a", nil)
checkSigValue(t, "Signature", sig.Bytes(), "01000000ff2aeb52f681793a00000000", nil)
}
func TestSealSignWithExtendedSessionSecurityKeyEx(t *testing.T) {
sealKey, _ := hex.DecodeString("59f600973cc4960a25480a7c196e4c58")
signKey, _ := hex.DecodeString("4788dc861b4782f35d43fd98fe1a2d39")
handle, _ := rc4Init(sealKey)
plaintext, _ := hex.DecodeString("50006c00610069006e007400650078007400")
seqNum := uint32(0)
flags := uint32(0)
flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags)
flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags)
sealed, sig := seal(flags, handle, signKey, seqNum, plaintext)
checkSigValue(t, "Sealed Data", sealed, "54e50165bf1936dc996020c1811b0f06fb5f", nil)
checkSigValue(t, "RC4 CheckSum", sig.CheckSum, "7fb38ec5c55d4976", nil)
checkSigValue(t, "Signature", sig.Bytes(), "010000007fb38ec5c55d497600000000", nil)
}

@ -1,283 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package negotiate_test
import (
"encoding/base64"
"flag"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"github.com/alexbrainman/sspi/negotiate"
)
var (
testURL = flag.String("url", "", "server URL for TestNegotiateHTTPClient")
)
// TODO: perhaps add Transport that is similar to http.Transport
// TODO: perhaps implement separate NTLMTransport and KerberosTransport (not sure about this idea)
// TODO: KerberosTransport is (I beleive) sinlge leg protocol, so it can be implemented easily (unlike NTLM)
// TODO: perhaps implement both server and client Transport
type httpClient struct {
client *http.Client
transport *http.Transport
url string
}
func newHTTPClient(url string) *httpClient {
transport := &http.Transport{
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
}
return &httpClient{
client: &http.Client{Transport: transport},
transport: transport,
url: url,
}
}
func (c *httpClient) CloseIdleConnections() {
c.transport.CloseIdleConnections()
}
func (c *httpClient) get(req *http.Request) (*http.Response, string, error) {
res, err := c.client.Do(req)
if err != nil {
return nil, "", err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, "", err
}
return res, string(body), nil
}
func (c *httpClient) canDoNegotiate() error {
req, err := http.NewRequest("GET", c.url, nil)
if err != nil {
return err
}
res, _, err := c.get(req)
if err != nil {
return err
}
if res.StatusCode != http.StatusUnauthorized {
return fmt.Errorf("Unauthorized expected, but got %v", res.StatusCode)
}
authHeaders, found := res.Header["Www-Authenticate"]
if !found {
return fmt.Errorf("Www-Authenticate not found")
}
for _, h := range authHeaders {
if h == "Negotiate" {
return nil
}
}
return fmt.Errorf("Www-Authenticate header does not contain Negotiate, but has %v", authHeaders)
}
func findAuthHeader(res *http.Response) ([]byte, error) {
authHeaders, found := res.Header["Www-Authenticate"]
if !found {
return nil, fmt.Errorf("Www-Authenticate not found")
}
if len(authHeaders) != 1 {
return nil, fmt.Errorf("Only one Www-Authenticate header expected, but %d found: %v", len(authHeaders), authHeaders)
}
if len(authHeaders[0]) < 10 {
return nil, fmt.Errorf("Www-Authenticate header is to short: %q", authHeaders[0])
}
if !strings.HasPrefix(authHeaders[0], "Negotiate ") {
return nil, fmt.Errorf("Www-Authenticate header is suppose to starts with \"Negotiate \", but is %q", authHeaders[0])
}
token, err := base64.StdEncoding.DecodeString(authHeaders[0][10:])
if err != nil {
return nil, err
}
return token, nil
}
func (c *httpClient) startAuthorization(inputToken []byte) ([]byte, error) {
req, err := http.NewRequest("GET", c.url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(inputToken))
res, _, err := c.get(req)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusUnauthorized {
return nil, fmt.Errorf("Unauthorized expected, but got %v", res.StatusCode)
}
outputToken, err := findAuthHeader(res)
if err != nil {
return nil, err
}
return outputToken, nil
}
func (c *httpClient) completeAuthorization(inputToken []byte) (*http.Response, string, error) {
req, err := http.NewRequest("GET", c.url, nil)
if err != nil {
return nil, "", err
}
req.Header.Set("Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(inputToken))
res, body, err := c.get(req)
if err != nil {
return nil, "", err
}
if res.StatusCode != http.StatusOK {
return nil, "", fmt.Errorf("OK expected, but got %v", res.StatusCode)
}
return res, body, nil
}
func TestNTLMHTTPClient(t *testing.T) {
// TODO: combine client and server tests so we don't need external server
if len(*testURL) == 0 {
t.Skip("Skipping due to empty \"url\" parameter")
}
cred, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer cred.Release()
secctx, clientToken1, err := negotiate.NewClientContext(cred, "")
if err != nil {
t.Fatal(err)
}
defer secctx.Release()
client := newHTTPClient(*testURL)
defer client.CloseIdleConnections()
err = client.canDoNegotiate()
if err != nil {
t.Fatal(err)
}
serverToken1, err := client.startAuthorization(clientToken1)
if err != nil {
t.Fatal(err)
}
authCompleted, clientToken2, err := secctx.Update(serverToken1)
if err != nil {
t.Fatal(err)
}
if len(clientToken2) == 0 {
t.Fatal("secctx.Update returns empty token for the peer, but our authentication is not done yet")
}
res, _, err := client.completeAuthorization(clientToken2)
if err != nil {
t.Fatal(err)
}
if authCompleted {
return
}
serverToken2, err := findAuthHeader(res)
if err != nil {
t.Fatal(err)
}
authCompleted, lastToken, err := secctx.Update(serverToken2)
if err != nil {
t.Fatal(err)
}
if !authCompleted {
t.Fatal("client authentication should be completed now")
}
if len(lastToken) > 0 {
t.Fatalf("last token supposed to be empty, but %v returned", lastToken)
}
}
func TestKerberosHTTPClient(t *testing.T) {
// TODO: combine client and server tests so we don't need external server
if len(*testURL) == 0 {
t.Skip("Skipping due to empty \"url\" parameter")
}
u, err := url.Parse(*testURL)
if err != nil {
t.Fatal(err)
}
targetName := "http/" + strings.ToUpper(u.Host)
cred, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer cred.Release()
secctx, token, err := negotiate.NewClientContext(cred, targetName)
if err != nil {
t.Fatal(err)
}
defer secctx.Release()
client := newHTTPClient(*testURL)
defer client.CloseIdleConnections()
err = client.canDoNegotiate()
if err != nil {
t.Fatal(err)
}
res, _, err := client.completeAuthorization(token)
if err != nil {
t.Fatal(err)
}
serverToken, err := findAuthHeader(res)
if err != nil {
t.Fatal(err)
}
authCompleted, lastToken, err := secctx.Update(serverToken)
if err != nil {
t.Fatal(err)
}
if !authCompleted {
t.Fatal("client authentication should be completed now")
}
if len(lastToken) > 0 {
t.Fatalf("last token supposed to be empty, but %v returned", lastToken)
}
}
// TODO: See http://www.innovation.ch/personal/ronald/ntlm.html#connections about needed to keep connection alive during authentication.
func TestNegotiateHTTPServer(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// TODO: implement Negotiate authentication here
w.Write([]byte("hello"))
}))
defer ts.Close()
res, err := http.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
got, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
if string(got) != "hello" {
t.Errorf("got %q, want hello", string(got))
}
}

@ -1,348 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
// Package negotiate provides access to the Microsoft Negotiate SSP Package.
//
package negotiate
import (
"errors"
"syscall"
"time"
"unsafe"
"github.com/alexbrainman/sspi"
)
// TODO: maybe (if possible) move all winapi related out of sspi and into sspi/internal/winapi
// PackageInfo contains Negotiate SSP package description.
var PackageInfo *sspi.PackageInfo
func init() {
var err error
PackageInfo, err = sspi.QueryPackageInfo(sspi.NEGOSSP_NAME)
if err != nil {
panic("failed to fetch Negotiate package info: " + err.Error())
}
}
func acquireCredentials(creduse uint32, ai *sspi.SEC_WINNT_AUTH_IDENTITY) (*sspi.Credentials, error) {
c, err := sspi.AcquireCredentials(sspi.NEGOSSP_NAME, creduse, (*byte)(unsafe.Pointer(ai)))
if err != nil {
return nil, err
}
return c, nil
}
// AcquireCurrentUserCredentials acquires credentials of currently
// logged on user. These will be used by the client to authenticate
// itself to the server. It will also be used by the server
// to impersonate the user.
func AcquireCurrentUserCredentials() (*sspi.Credentials, error) {
return acquireCredentials(sspi.SECPKG_CRED_OUTBOUND, nil)
}
// TODO: see if I can share this common ntlm and negotiate code
// AcquireUserCredentials acquires credentials of user described by
// domain, username and password. These will be used by the client to
// authenticate itself to the server. It will also be used by the
// server to impersonate the user.
func AcquireUserCredentials(domain, username, password string) (*sspi.Credentials, error) {
if len(domain) == 0 {
return nil, errors.New("domain parameter cannot be empty")
}
if len(username) == 0 {
return nil, errors.New("username parameter cannot be empty")
}
d, err := syscall.UTF16FromString(domain)
if err != nil {
return nil, err
}
u, err := syscall.UTF16FromString(username)
if err != nil {
return nil, err
}
var p []uint16
var plen uint32
if len(password) > 0 {
p, err = syscall.UTF16FromString(password)
if err != nil {
return nil, err
}
plen = uint32(len(p) - 1) // do not count terminating 0
}
ai := sspi.SEC_WINNT_AUTH_IDENTITY{
User: &u[0],
UserLength: uint32(len(u) - 1), // do not count terminating 0
Domain: &d[0],
DomainLength: uint32(len(d) - 1), // do not count terminating 0
Password: &p[0],
PasswordLength: plen,
Flags: sspi.SEC_WINNT_AUTH_IDENTITY_UNICODE,
}
return acquireCredentials(sspi.SECPKG_CRED_OUTBOUND, &ai)
}
// AcquireServerCredentials acquires server credentials that will
// be used to authenticate client.
func AcquireServerCredentials() (*sspi.Credentials, error) {
return acquireCredentials(sspi.SECPKG_CRED_INBOUND, nil)
}
func updateContext(c *sspi.Context, dst, src []byte, targetName *uint16) (authCompleted bool, n int, err error) {
var inBuf, outBuf [1]sspi.SecBuffer
inBuf[0].Set(sspi.SECBUFFER_TOKEN, src)
inBufs := &sspi.SecBufferDesc{
Version: sspi.SECBUFFER_VERSION,
BuffersCount: 1,
Buffers: &inBuf[0],
}
outBuf[0].Set(sspi.SECBUFFER_TOKEN, dst)
outBufs := &sspi.SecBufferDesc{
Version: sspi.SECBUFFER_VERSION,
BuffersCount: 1,
Buffers: &outBuf[0],
}
ret := c.Update(targetName, outBufs, inBufs)
switch ret {
case sspi.SEC_E_OK:
// session established -> return success
return true, int(outBuf[0].BufferSize), nil
case sspi.SEC_I_COMPLETE_NEEDED, sspi.SEC_I_COMPLETE_AND_CONTINUE:
ret = sspi.CompleteAuthToken(c.Handle, outBufs)
if ret != sspi.SEC_E_OK {
return false, 0, ret
}
case sspi.SEC_I_CONTINUE_NEEDED:
default:
return false, 0, ret
}
return false, int(outBuf[0].BufferSize), nil
}
func makeSignature(c *sspi.Context, msg []byte, qop, seqno uint32) ([]byte, error) {
_, maxSignature, _, _, err := c.Sizes()
if err != nil {
return nil, err
}
if maxSignature == 0 {
return nil, errors.New("integrity services are not requested or unavailable")
}
var b [2]sspi.SecBuffer
b[0].Set(sspi.SECBUFFER_DATA, msg)
b[1].Set(sspi.SECBUFFER_TOKEN, make([]byte, maxSignature))
ret := sspi.MakeSignature(c.Handle, qop, sspi.NewSecBufferDesc(b[:]), seqno)
if ret != sspi.SEC_E_OK {
return nil, ret
}
return b[1].Bytes(), nil
}
func verifySignature(c *sspi.Context, msg, token []byte, seqno uint32) (uint32, error) {
var b [2]sspi.SecBuffer
b[0].Set(sspi.SECBUFFER_DATA, msg)
b[1].Set(sspi.SECBUFFER_TOKEN, token)
var qop uint32
ret := sspi.VerifySignature(c.Handle, sspi.NewSecBufferDesc(b[:]), seqno, &qop)
if ret != sspi.SEC_E_OK {
return 0, ret
}
return qop, nil
}
// ClientContext is used by the client to manage all steps of Negotiate negotiation.
type ClientContext struct {
sctxt *sspi.Context
targetName *uint16
}
// NewClientContext creates new client context. It uses client
// credentials cred generated by AcquireCurrentUserCredentials or
// AcquireUserCredentials and SPN to start client Negotiate
// negotiation sequence. targetName is the service principal name
// (SPN) or the security context of the destination server.
// NewClientContext returns new token to be sent to the server.
func NewClientContext(cred *sspi.Credentials, targetName string) (cc *ClientContext, outputToken []byte, err error) {
var tname *uint16
if len(targetName) > 0 {
p, err2 := syscall.UTF16FromString(targetName)
if err2 != nil {
return nil, nil, err2
}
if len(p) > 0 {
tname = &p[0]
}
}
otoken := make([]byte, PackageInfo.MaxToken)
c := sspi.NewClientContext(cred, sspi.ISC_REQ_CONNECTION)
authCompleted, n, err2 := updateContext(c, otoken, nil, tname)
if err2 != nil {
return nil, nil, err2
}
if authCompleted {
c.Release()
return nil, nil, errors.New("negotiate authentication should not be completed yet")
}
if n == 0 {
c.Release()
return nil, nil, errors.New("negotiate token should not be empty")
}
otoken = otoken[:n]
return &ClientContext{sctxt: c, targetName: tname}, otoken, nil
}
// Release free up resources associated with client context c.
func (c *ClientContext) Release() error {
return c.sctxt.Release()
}
// Expiry returns c expiry time.
func (c *ClientContext) Expiry() time.Time {
return c.sctxt.Expiry()
}
// Update advances client part of Negotiate negotiation c. It uses
// token received from the server and returns true if client part
// of authentication is complete. It also returns new token to be
// sent to the server.
func (c *ClientContext) Update(token []byte) (authCompleted bool, outputToken []byte, err error) {
otoken := make([]byte, PackageInfo.MaxToken)
authDone, n, err2 := updateContext(c.sctxt, otoken, token, c.targetName)
if err2 != nil {
return false, nil, err2
}
if n == 0 && !authDone {
return false, nil, errors.New("negotiate token should not be empty")
}
otoken = otoken[:n]
return authDone, otoken, nil
}
// Sizes queries the client context for the sizes used in per-message
// functions. It returns the maximum token size used in authentication
// exchanges, the maximum signature size, the preferred integral size of
// messages, the size of any security trailer, and any error.
func (c *ClientContext) Sizes() (uint32, uint32, uint32, uint32, error) {
return c.sctxt.Sizes()
}
// MakeSignature uses the established client context to create a signature
// for the given message using the provided quality of protection flags and
// sequence number. It returns the signature token in addition to any error.
func (c *ClientContext) MakeSignature(msg []byte, qop, seqno uint32) ([]byte, error) {
return makeSignature(c.sctxt, msg, qop, seqno)
}
// VerifySignature uses the established client context and signature token
// to check that the provided message hasn't been tampered or received out
// of sequence. It returns any quality of protection flags and any error
// that occurred.
func (c *ClientContext) VerifySignature(msg, token []byte, seqno uint32) (uint32, error) {
return verifySignature(c.sctxt, msg, token, seqno)
}
// ServerContext is used by the server to manage all steps of Negotiate
// negotiation. Once authentication is completed the context can be
// used to impersonate client.
type ServerContext struct {
sctxt *sspi.Context
}
// TODO: I suspect NewServerContext might be the call to complete auth sometimes (see http://blogs.technet.com/b/tristank/archive/2006/08/02/negotiate-this.aspx) - we might need to redesign this call to return authCompleted or similar
// NewServerContext creates new server context. It uses server
// credentials created by AcquireServerCredentials and token from
// the client to start server Negotiate negotiation sequence.
// It also returns new token to be sent to the client.
func NewServerContext(cred *sspi.Credentials, token []byte) (sc *ServerContext, outputToken []byte, err error) {
otoken := make([]byte, PackageInfo.MaxToken)
c := sspi.NewServerContext(cred, sspi.ASC_REQ_CONNECTION)
authDone, n, err2 := updateContext(c, otoken, token, nil)
if err2 != nil {
return nil, nil, err2
}
if authDone {
c.Release()
return nil, nil, errors.New("negotiate authentication should not be completed yet")
}
if n == 0 {
c.Release()
return nil, nil, errors.New("negotiate token should not be empty")
}
otoken = otoken[:n]
return &ServerContext{sctxt: c}, otoken, nil
}
// Release free up resources associated with server context c.
func (c *ServerContext) Release() error {
return c.sctxt.Release()
}
// Expiry returns c expiry time.
func (c *ServerContext) Expiry() time.Time {
return c.sctxt.Expiry()
}
// Update advances server part of Negotiate negotiation c. It uses
// token received from the client and returns true if server part
// of authentication is complete. It also returns new token to be
// sent to the client.
func (c *ServerContext) Update(token []byte) (authCompleted bool, outputToken []byte, err error) {
otoken := make([]byte, PackageInfo.MaxToken)
authDone, n, err2 := updateContext(c.sctxt, otoken, token, nil)
if err2 != nil {
return false, nil, err2
}
if n == 0 && !authDone {
return false, nil, errors.New("negotiate token should not be empty")
}
otoken = otoken[:n]
return authDone, otoken, nil
}
// ImpersonateUser changes current OS thread user. New user is
// the user as specified by client credentials.
func (c *ServerContext) ImpersonateUser() error {
return c.sctxt.ImpersonateUser()
}
// RevertToSelf stops impersonation. It changes current OS thread
// user to what it was before ImpersonateUser was executed.
func (c *ServerContext) RevertToSelf() error {
return c.sctxt.RevertToSelf()
}
// Sizes queries the server context for the sizes used in per-message
// functions. It returns the maximum token size used in authentication
// exchanges, the maximum signature size, the preferred integral size of
// messages, the size of any security trailer, and any error.
func (c *ServerContext) Sizes() (uint32, uint32, uint32, uint32, error) {
return c.sctxt.Sizes()
}
// MakeSignature uses the established server context to create a signature
// for the given message using the provided quality of protection flags and
// sequence number. It returns the signature token in addition to any error.
func (c *ServerContext) MakeSignature(msg []byte, qop, seqno uint32) ([]byte, error) {
return makeSignature(c.sctxt, msg, qop, seqno)
}
// VerifySignature uses the established server context and signature token
// to check that the provided message hasn't been tampered or received out
// of sequence. It returns any quality of protection flags and any error
// that occurred.
func (c *ServerContext) VerifySignature(msg, token []byte, seqno uint32) (uint32, error) {
return verifySignature(c.sctxt, msg, token, seqno)
}

@ -1,312 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package negotiate_test
import (
"crypto/rand"
"flag"
"os"
"os/user"
"runtime"
"strings"
"syscall"
"testing"
"time"
"github.com/alexbrainman/sspi"
"github.com/alexbrainman/sspi/negotiate"
)
var (
testDomain = flag.String("domain", "", "domain parameter for TestAcquireUserCredentials")
testUsername = flag.String("username", "", "username parameter for TestAcquireUserCredentials")
testPassword = flag.String("password", "", "password parameter for TestAcquireUserCredentials")
)
func TestPackageInfo(t *testing.T) {
if negotiate.PackageInfo.Name != "Negotiate" {
t.Fatalf(`invalid Negotiate package name of %q, "Negotiate" is expected.`, negotiate.PackageInfo.Name)
}
}
func testContextExpiry(t *testing.T, name string, c interface {
Expiry() time.Time
}) {
validFor := c.Expiry().Sub(time.Now())
if validFor < time.Hour {
t.Errorf("%v expires in %v, more than 1 hour expected", name, validFor)
}
if validFor > 10*24*time.Hour {
t.Errorf("%v expires in %v, less than 10 days expected", name, validFor)
}
}
func testNegotiate(t *testing.T, clientCred *sspi.Credentials, SPN string) {
if len(SPN) == 0 {
t.Log("testing with blank SPN")
} else {
t.Logf("testing with SPN=%s", SPN)
}
serverCred, err := negotiate.AcquireServerCredentials()
if err != nil {
t.Fatal(err)
}
defer serverCred.Release()
client, toServerToken, err := negotiate.NewClientContext(clientCred, SPN)
if err != nil {
t.Fatal(err)
}
defer client.Release()
if len(toServerToken) == 0 {
t.Fatal("token for server cannot be empty")
}
t.Logf("sent %d bytes to server", len(toServerToken))
testContextExpiry(t, "client security context", client)
server, toClientToken, err := negotiate.NewServerContext(serverCred, toServerToken)
if err != nil {
t.Fatal(err)
}
defer server.Release()
testContextExpiry(t, "server security context", server)
var clientDone, serverDone bool
for {
if len(toClientToken) == 0 {
break
}
t.Logf("sent %d bytes to client", len(toClientToken))
clientDone, toServerToken, err = client.Update(toClientToken)
if err != nil {
t.Fatal(err)
}
if len(toServerToken) == 0 {
break
}
t.Logf("sent %d bytes to server", len(toServerToken))
serverDone, toClientToken, err = server.Update(toServerToken)
if err != nil {
t.Fatal(err)
}
}
if !clientDone {
t.Fatal("client authentication should be completed now")
}
if !serverDone {
t.Fatal("server authentication should be completed now")
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err = server.ImpersonateUser()
if err != nil {
t.Fatal(err)
}
defer server.RevertToSelf()
_, err = user.Current()
if err != nil {
t.Fatal(err)
}
}
func TestNegotiate(t *testing.T) {
cred, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer cred.Release()
testNegotiate(t, cred, "")
hostname, err := os.Hostname()
if err != nil {
t.Fatal(err)
}
testNegotiate(t, cred, "HOST/"+strings.ToUpper(hostname))
testNegotiate(t, cred, "HOST/127.0.0.1")
}
func TestNegotiateFailure(t *testing.T) {
clientCred, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer clientCred.Release()
serverCred, err := negotiate.AcquireServerCredentials()
if err != nil {
t.Fatal(err)
}
defer serverCred.Release()
client, toServerToken, err := negotiate.NewClientContext(clientCred, "HOST/UNKNOWN_HOST_NAME")
if err != nil {
t.Fatal(err)
}
defer client.Release()
if len(toServerToken) == 0 {
t.Fatal("token for server cannot be empty")
}
t.Logf("sent %d bytes to server", len(toServerToken))
server, toClientToken, err := negotiate.NewServerContext(serverCred, toServerToken)
if err != nil {
t.Fatal(err)
}
defer server.Release()
for {
var clientDone, serverDone bool
if len(toClientToken) == 0 {
t.Fatal("token for client cannot be empty")
}
t.Logf("sent %d bytes to client", len(toClientToken))
clientDone, toServerToken, err = client.Update(toClientToken)
if err != nil {
t.Fatal(err)
}
t.Logf("clientDone=%v serverDone=%v", clientDone, serverDone)
if clientDone {
// t.Fatal("client authentication cannot be completed")
}
if len(toServerToken) == 0 {
t.Fatal("token for server cannot be empty")
}
t.Logf("sent %d bytes to server", len(toServerToken))
serverDone, toClientToken, err = server.Update(toServerToken)
if err != nil {
if err == sspi.SEC_E_LOGON_DENIED {
return
}
t.Fatalf("unexpected failure 0x%x: %v", uintptr(err.(syscall.Errno)), err)
}
t.Logf("clientDone=%v serverDone=%v", clientDone, serverDone)
if serverDone {
t.Fatal("server authentication cannot be completed")
}
}
}
func TestAcquireUserCredentials(t *testing.T) {
if len(*testDomain) == 0 {
t.Skip("Skipping due to empty \"domain\" parameter")
}
if len(*testUsername) == 0 {
t.Skip("Skipping due to empty \"username\" parameter")
}
if len(*testPassword) == 0 {
t.Skip("Skipping due to empty \"password\" parameter")
}
cred, err := negotiate.AcquireUserCredentials(*testDomain, *testUsername, *testPassword)
if err != nil {
t.Fatal(err)
}
defer cred.Release()
testNegotiate(t, cred, "")
}
func TestSignature(t *testing.T) {
clientCred, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer clientCred.Release()
serverCred, err := negotiate.AcquireServerCredentials()
if err != nil {
t.Fatal(err)
}
defer serverCred.Release()
client, toServerToken, err := negotiate.NewClientContext(clientCred, "")
if err != nil {
t.Fatal(err)
}
defer client.Release()
if len(toServerToken) == 0 {
t.Fatal("token for server cannot be empty")
}
server, toClientToken, err := negotiate.NewServerContext(serverCred, toServerToken)
if err != nil {
t.Fatal(err)
}
defer server.Release()
var clientDone, serverDone bool
for {
if len(toClientToken) == 0 {
break
}
clientDone, toServerToken, err = client.Update(toClientToken)
if err != nil {
t.Fatal(err)
}
if len(toServerToken) == 0 {
break
}
serverDone, toClientToken, err = server.Update(toServerToken)
if err != nil {
t.Fatal(err)
}
}
if !clientDone {
t.Fatal("client authentication should be completed now")
}
if !serverDone {
t.Fatal("server authentication should be completed now")
}
clientMsg := make([]byte, 10)
_, err = rand.Read(clientMsg)
if err != nil {
t.Fatal(err)
}
t.Logf("clientMsg=%v", clientMsg)
clientSig, err := client.MakeSignature(clientMsg, 0, 0)
if err != nil {
t.Fatal(err)
}
t.Logf("clientSig=%v", clientSig)
_, err = server.VerifySignature(clientMsg, clientSig, 0)
if err != nil {
t.Fatal(err)
}
t.Logf("server verified client signature")
serverMsg := make([]byte, 10)
_, err = rand.Read(serverMsg)
if err != nil {
t.Fatal(err)
}
t.Logf("serverMsg=%v", serverMsg)
serverSig, err := server.MakeSignature(serverMsg, 0, 0)
if err != nil {
t.Fatal(err)
}
t.Logf("serverSig=%v", serverSig)
_, err = client.VerifySignature(serverMsg, serverSig, 0)
if err != nil {
t.Fatal(err)
}
t.Logf("client verified server signature")
}

@ -1,177 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package ntlm_test
import (
"encoding/base64"
"flag"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/alexbrainman/sspi/ntlm"
)
var (
testURL = flag.String("url", "", "server URL for TestNTLMHTTPClient")
)
func newRequest() (*http.Request, error) {
req, err := http.NewRequest("GET", *testURL, nil)
if err != nil {
return nil, err
}
return req, nil
}
func get(req *http.Request) (*http.Response, string, error) {
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, "", err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, "", err
}
return res, string(body), nil
}
func canDoNTLM() error {
req, err := newRequest()
if err != nil {
return err
}
res, _, err := get(req)
if err != nil {
return err
}
if res.StatusCode != http.StatusUnauthorized {
return fmt.Errorf("Unauthorized expected, but got %v", res.StatusCode)
}
authHeaders, found := res.Header["Www-Authenticate"]
if !found {
return fmt.Errorf("Www-Authenticate not found")
}
for _, h := range authHeaders {
if h == "NTLM" {
return nil
}
}
return fmt.Errorf("Www-Authenticate header does not contain NTLM, but has %v", authHeaders)
}
func doNTLMNegotiate(negotiate []byte) ([]byte, error) {
req, err := newRequest()
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(negotiate))
res, _, err := get(req)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusUnauthorized {
return nil, fmt.Errorf("Unauthorized expected, but got %v", res.StatusCode)
}
authHeaders, found := res.Header["Www-Authenticate"]
if !found {
return nil, fmt.Errorf("Www-Authenticate not found")
}
if len(authHeaders) != 1 {
return nil, fmt.Errorf("Only one Www-Authenticate header expected, but %d found: %v", len(authHeaders), authHeaders)
}
if len(authHeaders[0]) < 6 {
return nil, fmt.Errorf("Www-Authenticate header is to short: %q", authHeaders[0])
}
if !strings.HasPrefix(authHeaders[0], "NTLM ") {
return nil, fmt.Errorf("Www-Authenticate header is suppose to starts with \"NTLM \", but is %q", authHeaders[0])
}
authenticate, err := base64.StdEncoding.DecodeString(authHeaders[0][5:])
if err != nil {
return nil, err
}
return authenticate, nil
}
func doNTLMAuthenticate(authenticate []byte) (string, error) {
req, err := newRequest()
if err != nil {
return "", err
}
req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(authenticate))
res, body, err := get(req)
if err != nil {
return "", err
}
if res.StatusCode != http.StatusOK {
return "", fmt.Errorf("OK expected, but got %v", res.StatusCode)
}
return body, nil
}
func TestNTLMHTTPClient(t *testing.T) {
// TODO: combine client and server tests so we don't need external server
if len(*testURL) == 0 {
t.Skip("Skipping due to empty \"url\" parameter")
}
cred, err := ntlm.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer cred.Release()
secctx, negotiate, err := ntlm.NewClientContext(cred)
if err != nil {
t.Fatal(err)
}
defer secctx.Release()
err = canDoNTLM()
if err != nil {
t.Fatal(err)
}
challenge, err := doNTLMNegotiate(negotiate)
if err != nil {
t.Fatal(err)
}
authenticate, err := secctx.Update(challenge)
if err != nil {
t.Fatal(err)
}
_, err = doNTLMAuthenticate(authenticate)
if err != nil {
t.Fatal(err)
}
}
// TODO: See http://www.innovation.ch/personal/ronald/ntlm.html#connections about needed to keep connection alive during authentication.
func TestNTLMHTTPServer(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// TODO: implement NTLM authentication here
w.Write([]byte("hello"))
}))
defer ts.Close()
res, err := http.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
got, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
if string(got) != "hello" {
t.Errorf("got %q, want hello", string(got))
}
}

@ -1,119 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package ntlm_test
import (
"flag"
"os/user"
"runtime"
"testing"
"time"
"github.com/alexbrainman/sspi"
"github.com/alexbrainman/sspi/ntlm"
)
var (
testDomain = flag.String("domain", "", "domain parameter for TestAcquireUserCredentials")
testUsername = flag.String("username", "", "username parameter for TestAcquireUserCredentials")
testPassword = flag.String("password", "", "password parameter for TestAcquireUserCredentials")
)
func TestPackageInfo(t *testing.T) {
if ntlm.PackageInfo.Name != "NTLM" {
t.Fatalf(`invalid NTLM package name of %q, "NTLM" is expected.`, ntlm.PackageInfo.Name)
}
}
func testContextExpiry(t *testing.T, name string, c interface {
Expiry() time.Time
}) {
validFor := c.Expiry().Sub(time.Now())
if validFor < time.Hour {
t.Errorf("%v exipries in %v, more then 1 hour expected", name, validFor)
}
if validFor > 10*24*time.Hour {
t.Errorf("%v exipries in %v, less then 10 days expected", name, validFor)
}
}
func testNTLM(t *testing.T, clientCred *sspi.Credentials) {
serverCred, err := ntlm.AcquireServerCredentials()
if err != nil {
t.Fatal(err)
}
defer serverCred.Release()
client, token1, err := ntlm.NewClientContext(clientCred)
if err != nil {
t.Fatal(err)
}
defer client.Release()
testContextExpiry(t, "clent security context", client)
server, token2, err := ntlm.NewServerContext(serverCred, token1)
if err != nil {
t.Fatal(err)
}
defer server.Release()
testContextExpiry(t, "server security context", server)
token3, err := client.Update(token2)
if err != nil {
t.Fatal(err)
}
err = server.Update(token3)
if err != nil {
t.Fatal(err)
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err = server.ImpersonateUser()
if err != nil {
t.Fatal(err)
}
defer server.RevertToSelf()
_, err = user.Current()
if err != nil {
t.Fatal(err)
}
}
func TestNTLM(t *testing.T) {
cred, err := ntlm.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer cred.Release()
testNTLM(t, cred)
}
func TestAcquireUserCredentials(t *testing.T) {
if len(*testDomain) == 0 {
t.Skip("Skipping due to empty \"domain\" parameter")
}
if len(*testUsername) == 0 {
t.Skip("Skipping due to empty \"username\" parameter")
}
if len(*testPassword) == 0 {
t.Skip("Skipping due to empty \"password\" parameter")
}
cred, err := ntlm.AcquireUserCredentials(*testDomain, *testUsername, *testPassword)
if err != nil {
t.Fatal(err)
}
defer cred.Release()
testNTLM(t, cred)
}

@ -1,82 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package schannel
import (
"syscall"
"unsafe"
"github.com/alexbrainman/sspi"
)
// TODO: maybe move all these into a separate package or something
func (c *Client) streamSizes() (*_SecPkgContext_StreamSizes, error) {
// TODO: do not retrive _SecPkgContext_StreamSizes every time (cache the data and invalidate it every time is possible can be changed: handshake, redo, ...)
// TODO: maybe return (header, trailer, maxmsg int, err error) instead
// TODO: maybe this needs to be exported
var ss _SecPkgContext_StreamSizes
ret := sspi.QueryContextAttributes(c.ctx.Handle, _SECPKG_ATTR_STREAM_SIZES, (*byte)(unsafe.Pointer(&ss)))
if ret != sspi.SEC_E_OK {
return nil, ret
}
return &ss, nil
}
func (c *Client) ProtocolInfo() (name string, major, minor uint32, err error) {
var pi _SecPkgContext_ProtoInfo
ret := sspi.QueryContextAttributes(c.ctx.Handle, _SECPKG_ATTR_PROTO_INFO, (*byte)(unsafe.Pointer(&pi)))
if ret != sspi.SEC_E_OK {
return "", 0, 0, ret
}
defer sspi.FreeContextBuffer((*byte)(unsafe.Pointer(pi.ProtocolName)))
s := syscall.UTF16ToString((*[2 << 20]uint16)(unsafe.Pointer(pi.ProtocolName))[:])
return s, pi.MajorVersion, pi.MinorVersion, nil
}
func (c *Client) UserName() (string, error) {
var ns _SecPkgContext_Names
ret := sspi.QueryContextAttributes(c.ctx.Handle, _SECPKG_ATTR_NAMES, (*byte)(unsafe.Pointer(&ns)))
if ret != sspi.SEC_E_OK {
return "", ret
}
defer sspi.FreeContextBuffer((*byte)(unsafe.Pointer(ns.UserName)))
s := syscall.UTF16ToString((*[2 << 20]uint16)(unsafe.Pointer(ns.UserName))[:])
return s, nil
}
func (c *Client) AuthorityName() (string, error) {
var a _SecPkgContext_Authority
ret := sspi.QueryContextAttributes(c.ctx.Handle, _SECPKG_ATTR_AUTHORITY, (*byte)(unsafe.Pointer(&a)))
if ret != sspi.SEC_E_OK {
return "", ret
}
defer sspi.FreeContextBuffer((*byte)(unsafe.Pointer(a.AuthorityName)))
s := syscall.UTF16ToString((*[2 << 20]uint16)(unsafe.Pointer(a.AuthorityName))[:])
return s, nil
}
func (c *Client) KeyInfo() (sessionKeySize uint32, sigAlg uint32, sigAlgName string, encAlg uint32, encAlgName string, err error) {
var ki _SecPkgContext_KeyInfo
ret := sspi.QueryContextAttributes(c.ctx.Handle, _SECPKG_ATTR_KEY_INFO, (*byte)(unsafe.Pointer(&ki)))
if ret != sspi.SEC_E_OK {
return 0, 0, "", 0, "", ret
}
defer sspi.FreeContextBuffer((*byte)(unsafe.Pointer(ki.SignatureAlgorithmName)))
defer sspi.FreeContextBuffer((*byte)(unsafe.Pointer(ki.EncryptAlgorithmName)))
saname := syscall.UTF16ToString((*[2 << 20]uint16)(unsafe.Pointer(ki.SignatureAlgorithmName))[:])
eaname := syscall.UTF16ToString((*[2 << 20]uint16)(unsafe.Pointer(ki.EncryptAlgorithmName))[:])
return ki.KeySize, ki.SignatureAlgorithm, saname, ki.EncryptAlgorithm, eaname, nil
}
// Sizes queries the context for the sizes used in per-message functions.
// It returns the maximum token size used in authentication exchanges, the
// maximum signature size, the preferred integral size of messages, the
// size of any security trailer, and any error.
func (c *Client) Sizes() (uint32, uint32, uint32, uint32, error) {
return c.ctx.Sizes()
}

@ -1,78 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package schannel
import (
"io"
"github.com/alexbrainman/sspi"
)
type inputBuffer struct {
data []byte
reader io.Reader
}
func newInputBuffer(initialsize int, reader io.Reader) *inputBuffer {
return &inputBuffer{
data: make([]byte, 0, initialsize),
reader: reader,
}
}
// copy copies data d into buffer ib. copy grows destination if needed.
func (ib *inputBuffer) copy(d []byte) int {
// TODO: check all call sites, maybe this can be made more efficient
return copy(ib.data, d)
}
func (ib *inputBuffer) reset() {
ib.data = ib.data[:0]
}
func (ib *inputBuffer) grow() {
b := make([]byte, len(ib.data), cap(ib.data)*2)
copy(b, ib.data)
ib.data = b
}
func (ib *inputBuffer) readMore() error {
if len(ib.data) == cap(ib.data) {
ib.grow()
}
n0 := len(ib.data)
ib.data = ib.data[:cap(ib.data)]
n, err := ib.reader.Read(ib.data[n0:])
if err != nil {
return err
}
ib.data = ib.data[:n0+n]
return nil
}
func (ib *inputBuffer) bytes() []byte {
return ib.data
}
func sendOutBuffer(w io.Writer, b *sspi.SecBuffer) error {
_, err := b.WriteAll(w)
// TODO: see if I can preallocate buffers instead
b.Free()
b.Set(sspi.SECBUFFER_TOKEN, nil)
return err
}
// indexOfSecBuffer searches buffers bs for buffer type buftype.
// It returns -1 if not found.
func indexOfSecBuffer(bs []sspi.SecBuffer, buftype uint32) int {
for i := range bs {
if bs[i].BufferType == buftype {
return i
}
}
return -1
}

@ -1,276 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package schannel
import (
"errors"
"io"
"syscall"
"unsafe"
"github.com/alexbrainman/sspi"
)
// TODO: add documentation
// TODO: maybe come up with a better name
type Client struct {
ctx *sspi.Context
conn io.ReadWriter
inbuf *inputBuffer
}
func NewClientContext(cred *sspi.Credentials, conn io.ReadWriter) *Client {
return &Client{
ctx: sspi.NewClientContext(cred, sspi.ISC_REQ_STREAM|sspi.ISC_REQ_ALLOCATE_MEMORY|sspi.ISC_REQ_EXTENDED_ERROR|sspi.ISC_REQ_MANUAL_CRED_VALIDATION),
conn: conn,
// TODO: decide how large this buffer needs to be (it cannot be too small otherwise messages won't fit)
inbuf: newInputBuffer(1000, conn),
}
}
func (c *Client) Handshake(serverName string) error {
name, err := syscall.UTF16PtrFromString(serverName)
if err != nil {
return err
}
inBuf := []sspi.SecBuffer{
{BufferType: sspi.SECBUFFER_TOKEN},
{BufferType: sspi.SECBUFFER_EMPTY},
}
// TODO: InitializeSecurityContext doco says that inBufs should be nil on the first call
inBufs := sspi.NewSecBufferDesc(inBuf[:])
outBuf := []sspi.SecBuffer{
{BufferType: sspi.SECBUFFER_TOKEN},
}
outBufs := sspi.NewSecBufferDesc(outBuf)
for {
ret := c.ctx.Update(name, outBufs, inBufs)
// send data to peer
err := sendOutBuffer(c.conn, &outBuf[0])
if err != nil {
return err
}
// update input buffer
fetchMore := true
switch ret {
case sspi.SEC_E_OK, sspi.SEC_I_CONTINUE_NEEDED:
if inBuf[1].BufferType == sspi.SECBUFFER_EXTRA {
c.inbuf.copy(inBuf[1].Bytes())
fetchMore = false
} else {
c.inbuf.reset()
}
}
// decide what to do next
switch ret {
case sspi.SEC_E_OK:
// negotiation is competed
return nil
case sspi.SEC_I_CONTINUE_NEEDED, sspi.SEC_E_INCOMPLETE_MESSAGE:
// continue on
default:
return ret
}
// fetch more input data if needed
if fetchMore {
err := c.inbuf.readMore()
if err != nil {
return err
}
}
inBuf[0].Set(sspi.SECBUFFER_TOKEN, c.inbuf.bytes())
inBuf[1].Set(sspi.SECBUFFER_EMPTY, nil)
}
}
// TODO: protect Handshake, Read, Write and Shutdown with locks
// TODO: call Handshake at the start Read and Write unless handshake is already complete
func (c *Client) writeBlock(data []byte) (int, error) {
ss, err := c.streamSizes()
if err != nil {
return 0, err
}
// TODO: maybe make this buffer (and header and trailer buffers) part of Context struct
var b [4]sspi.SecBuffer
b[0].Set(sspi.SECBUFFER_STREAM_HEADER, make([]byte, ss.Header))
b[1].Set(sspi.SECBUFFER_DATA, data)
b[2].Set(sspi.SECBUFFER_STREAM_TRAILER, make([]byte, ss.Trailer))
b[3].Set(sspi.SECBUFFER_EMPTY, nil)
ret := sspi.EncryptMessage(c.ctx.Handle, 0, sspi.NewSecBufferDesc(b[:]), 0)
switch ret {
case sspi.SEC_E_OK:
case sspi.SEC_E_CONTEXT_EXPIRED:
// TODO: handle this
panic("writeBlock: SEC_E_CONTEXT_EXPIRED")
default:
return 0, ret
}
n1, err := b[0].WriteAll(c.conn)
if err != nil {
return n1, err
}
n2, err := b[1].WriteAll(c.conn)
if err != nil {
return n1 + n2, err
}
n3, err := b[2].WriteAll(c.conn)
return n1 + n2 + n3, err
}
func (c *Client) Write(b []byte) (int, error) {
ss, err := c.streamSizes()
if err != nil {
return 0, err
}
// TODO: handle redoing context here
total := 0
for len(b) > 0 {
// TODO: maybe use ss.BlockSize to decide on optimum block size
b2 := b
if len(b) > int(ss.MaximumMessage) {
b2 = b2[:ss.MaximumMessage]
}
n, err := c.writeBlock(b2)
total += n
if err != nil {
return total, err
}
b = b[len(b2):]
}
return total, nil
}
func (c *Client) Read(data []byte) (int, error) {
if len(c.inbuf.bytes()) == 0 {
err := c.inbuf.readMore()
if err != nil {
return 0, err
}
}
var b [4]sspi.SecBuffer
desc := sspi.NewSecBufferDesc(b[:])
loop:
for {
b[0].Set(sspi.SECBUFFER_DATA, c.inbuf.bytes())
b[1].Set(sspi.SECBUFFER_EMPTY, nil)
b[2].Set(sspi.SECBUFFER_EMPTY, nil)
b[3].Set(sspi.SECBUFFER_EMPTY, nil)
ret := sspi.DecryptMessage(c.ctx.Handle, desc, 0, nil)
switch ret {
case sspi.SEC_E_OK:
break loop
case sspi.SEC_E_INCOMPLETE_MESSAGE:
// TODO: it seems b[0].BufferSize or b[1].BufferSize contains "how many more bytes needed for full message" - maybe use it somehow
// read more and try again
err := c.inbuf.readMore()
if err != nil {
return 0, err
}
default:
// TODO: handle other ret values
return 0, errors.New("not implemented")
}
}
i := indexOfSecBuffer(b[:], sspi.SECBUFFER_DATA)
if i == -1 {
return 0, errors.New("DecryptMessage did not return SECBUFFER_DATA")
}
n := copy(data, b[i].Bytes())
i = indexOfSecBuffer(b[:], sspi.SECBUFFER_EXTRA)
if i == -1 {
c.inbuf.reset()
} else {
c.inbuf.copy(b[i].Bytes())
}
return n, nil
}
func (c *Client) applyShutdownControlToken() error {
data := uint32(_SCHANNEL_SHUTDOWN)
b := sspi.SecBuffer{
BufferType: sspi.SECBUFFER_TOKEN,
Buffer: (*byte)(unsafe.Pointer(&data)),
BufferSize: uint32(unsafe.Sizeof(data)),
}
desc := sspi.SecBufferDesc{
Version: sspi.SECBUFFER_VERSION,
BuffersCount: 1,
Buffers: &b,
}
ret := sspi.ApplyControlToken(c.ctx.Handle, &desc)
if ret != sspi.SEC_E_OK {
return ret
}
return nil
}
func (c *Client) Shutdown() error {
err := c.applyShutdownControlToken()
if err != nil {
return err
}
inBuf := []sspi.SecBuffer{
{BufferType: sspi.SECBUFFER_TOKEN},
{BufferType: sspi.SECBUFFER_EMPTY},
}
inBufs := sspi.NewSecBufferDesc(inBuf[:])
outBuf := []sspi.SecBuffer{
{BufferType: sspi.SECBUFFER_TOKEN},
}
outBufs := sspi.NewSecBufferDesc(outBuf)
for {
// TODO: I am not sure if I can pass nil as targname
ret := c.ctx.Update(nil, outBufs, inBufs)
// send data to peer
err := sendOutBuffer(c.conn, &outBuf[0])
if err != nil {
return err
}
// update input buffer
fetchMore := true
switch ret {
case sspi.SEC_E_OK, sspi.SEC_I_CONTINUE_NEEDED:
if inBuf[1].BufferType == sspi.SECBUFFER_EXTRA {
c.inbuf.copy(inBuf[1].Bytes())
fetchMore = false
} else {
c.inbuf.reset()
}
}
// decide what to do next
switch ret {
case sspi.SEC_E_OK, sspi.SEC_E_CONTEXT_EXPIRED:
// shutdown is competed
return nil
case sspi.SEC_I_CONTINUE_NEEDED, sspi.SEC_E_INCOMPLETE_MESSAGE:
// continue on
default:
return ret
}
// fetch more input data if needed
if fetchMore {
err := c.inbuf.readMore()
if err != nil {
return err
}
}
inBuf[0].Set(sspi.SECBUFFER_TOKEN, c.inbuf.bytes())
inBuf[1].Set(sspi.SECBUFFER_EMPTY, nil)
}
}

@ -1,47 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
// Package schannel provides access to the Secure Channel SSP Package.
//
package schannel
import (
"unsafe"
"github.com/alexbrainman/sspi"
)
// TODO: add documentation
// PackageInfo contains Secure Channel SSP package description.
var PackageInfo *sspi.PackageInfo
func init() {
var err error
PackageInfo, err = sspi.QueryPackageInfo(sspi.UNISP_NAME)
if err != nil {
panic("failed to fetch Schannel package info: " + err.Error())
}
}
func acquireCredentials(creduse uint32) (*sspi.Credentials, error) {
sc := &__SCHANNEL_CRED{
Version: __SCHANNEL_CRED_VERSION,
// TODO: allow for Creds / CredCount
// TODO: allow for RootStore
// TODO: allow for EnabledProtocols
// TODO: allow for MinimumCipherStrength / MaximumCipherStrength
}
c, err := sspi.AcquireCredentials(sspi.UNISP_NAME, creduse, (*byte)(unsafe.Pointer(sc)))
if err != nil {
return nil, err
}
return c, nil
}
func AcquireClientCredentials() (*sspi.Credentials, error) {
return acquireCredentials(sspi.SECPKG_CRED_OUTBOUND)
}

@ -1,77 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package schannel_test
import (
"fmt"
"io/ioutil"
"net"
"testing"
"github.com/alexbrainman/sspi/schannel"
)
func TestPackageInfo(t *testing.T) {
want := "Microsoft Unified Security Protocol Provider"
if schannel.PackageInfo.Name != want {
t.Fatalf(`invalid Schannel package name of %q, %q is expected.`, schannel.PackageInfo.Name, want)
}
}
func TestSchannel(t *testing.T) {
cred, err := schannel.AcquireClientCredentials()
if err != nil {
t.Fatal(err)
}
defer cred.Release()
conn, err := net.Dial("tcp", "microsoft.com:https")
if err != nil {
t.Fatal(err)
}
defer conn.Close()
client := schannel.NewClientContext(cred, conn)
err = client.Handshake("microsoft.com")
if err != nil {
t.Fatal(err)
}
protoName, major, minor, err := client.ProtocolInfo()
if err != nil {
t.Fatal(err)
}
t.Logf("protocol info: %s %d.%d", protoName, major, minor)
userName, err := client.UserName()
if err != nil {
t.Fatal(err)
}
t.Logf("user name: %q", userName)
authorityName, err := client.AuthorityName()
if err != nil {
t.Fatal(err)
}
t.Logf("authority name: %q", authorityName)
sessionKeySize, sigAlg, sigAlgName, encAlg, encAlgName, err := client.KeyInfo()
if err != nil {
t.Fatal(err)
}
t.Logf("key info: session_key_size=%d signature_alg=%q(%d) encryption_alg=%q(%d)", sessionKeySize, sigAlgName, sigAlg, encAlgName, encAlg)
// TODO: add some code to verify if negotiated connection is suitable (ciper and so on)
_, err = fmt.Fprintf(client, "GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
if err != nil {
t.Fatal(err)
}
data, err := ioutil.ReadAll(client)
if err != nil {
t.Fatal(err)
}
t.Logf("web page: %q", data)
err = client.Shutdown()
if err != nil {
t.Fatal(err)
}
}

@ -1,109 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package schannel
import (
"syscall"
)
// TODO: maybe put all these into a separate package, like sspi/schannel/winapi or similar
const (
__SCHANNEL_CRED_VERSION = 4
_SP_PROT_PCT1_SERVER = 0x00000001
_SP_PROT_PCT1_CLIENT = 0x00000002
_SP_PROT_PCT1 = _SP_PROT_PCT1_SERVER | _SP_PROT_PCT1_CLIENT
_SP_PROT_SSL2_SERVER = 0x00000004
_SP_PROT_SSL2_CLIENT = 0x00000008
_SP_PROT_SSL2 = _SP_PROT_SSL2_SERVER | _SP_PROT_SSL2_CLIENT
_SP_PROT_SSL3_SERVER = 0x00000010
_SP_PROT_SSL3_CLIENT = 0x00000020
_SP_PROT_SSL3 = _SP_PROT_SSL3_SERVER | _SP_PROT_SSL3_CLIENT
_SP_PROT_TLS1_SERVER = 0x00000040
_SP_PROT_TLS1_CLIENT = 0x00000080
_SP_PROT_TLS1 = _SP_PROT_TLS1_SERVER | _SP_PROT_TLS1_CLIENT
_SP_PROT_SSL3TLS1_CLIENTS = _SP_PROT_TLS1_CLIENT | _SP_PROT_SSL3_CLIENT
_SP_PROT_SSL3TLS1_SERVERS = _SP_PROT_TLS1_SERVER | _SP_PROT_SSL3_SERVER
_SP_PROT_SSL3TLS1 = _SP_PROT_SSL3 | _SP_PROT_TLS1
)
type __SCHANNEL_CRED struct {
Version uint32
CredCount uint32
Creds *syscall.CertContext
RootStore syscall.Handle // TODO: make sure this field is syscall.Handle
cMappers uint32
aphMappers uintptr
SupportedAlgCount uint32
SupportedAlgs *uint32
EnabledProtocols uint32
MinimumCipherStrength uint32
MaximumCipherStrength uint32
SessionLifespan uint32
Flags uint32
CredFormat uint32
}
const (
_SECPKG_ATTR_SIZES = 0
_SECPKG_ATTR_NAMES = 1
_SECPKG_ATTR_LIFESPAN = 2
_SECPKG_ATTR_DCE_INFO = 3
_SECPKG_ATTR_STREAM_SIZES = 4
_SECPKG_ATTR_KEY_INFO = 5
_SECPKG_ATTR_AUTHORITY = 6
_SECPKG_ATTR_PROTO_INFO = 7
_SECPKG_ATTR_PASSWORD_EXPIRY = 8
_SECPKG_ATTR_SESSION_KEY = 9
_SECPKG_ATTR_PACKAGE_INFO = 10
_SECPKG_ATTR_USER_FLAGS = 11
_SECPKG_ATTR_NEGOTIATION_INFO = 12
_SECPKG_ATTR_NATIVE_NAMES = 13
_SECPKG_ATTR_FLAGS = 14
_SCHANNEL_RENEGOTIATE = 0
_SCHANNEL_SHUTDOWN = 1
_SCHANNEL_ALERT = 2
)
type _SecPkgContext_StreamSizes struct {
Header uint32
Trailer uint32
MaximumMessage uint32
Buffers uint32
BlockSize uint32
}
type _SecPkgContext_ProtoInfo struct {
ProtocolName *uint16
MajorVersion uint32
MinorVersion uint32
}
type _SecPkgContext_Names struct {
UserName *uint16
}
type _SecPkgContext_Authority struct {
AuthorityName *uint16
}
type _SecPkgContext_KeyInfo struct {
SignatureAlgorithmName *uint16
EncryptAlgorithmName *uint16
KeySize uint32
SignatureAlgorithm uint32
EncryptAlgorithm uint32
}
// TODO: SecPkgContext_ConnectionInfo
// TODO: SECPKG_ATTR_REMOTE_CERT_CONTEXT
// TODO: SECPKG_ATTR_LOCAL_CERT_CONTEXT
// TODO: SecPkgContext_IssuerListInfoEx

@ -1,33 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package sspi_test
import (
"testing"
"github.com/alexbrainman/sspi"
)
func TestQueryPackageInfo(t *testing.T) {
pkgnames := []string{
sspi.NTLMSP_NAME,
sspi.MICROSOFT_KERBEROS_NAME,
sspi.NEGOSSP_NAME,
sspi.UNISP_NAME,
}
for _, name := range pkgnames {
pi, err := sspi.QueryPackageInfo(name)
if err != nil {
t.Error(err)
continue
}
if pi.Name != name {
t.Errorf("unexpected package name %q returned for %q package: package info is %#v", pi.Name, name, pi)
continue
}
}
}

@ -1,121 +0,0 @@
package gitobj
import (
"bytes"
"errors"
"io/ioutil"
"strings"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBlobReturnsCorrectObjectType(t *testing.T) {
assert.Equal(t, BlobObjectType, new(Blob).Type())
}
func TestBlobFromString(t *testing.T) {
given := []byte("example")
glen := len(given)
b := NewBlobFromBytes(given)
assert.EqualValues(t, glen, b.Size)
contents, err := ioutil.ReadAll(b.Contents)
assert.NoError(t, err)
assert.Equal(t, given, contents)
}
func TestBlobEncoding(t *testing.T) {
const contents = "Hello, world!\n"
b := &Blob{
Size: int64(len(contents)),
Contents: strings.NewReader(contents),
}
var buf bytes.Buffer
if _, err := b.Encode(&buf); err != nil {
t.Fatal(err.Error())
}
assert.Equal(t, contents, (&buf).String())
}
func TestBlobDecoding(t *testing.T) {
const contents = "Hello, world!\n"
from := strings.NewReader(contents)
b := new(Blob)
n, err := b.Decode(from, int64(len(contents)))
assert.Equal(t, 0, n)
assert.Nil(t, err)
assert.EqualValues(t, len(contents), b.Size)
got, err := ioutil.ReadAll(b.Contents)
assert.Nil(t, err)
assert.Equal(t, []byte(contents), got)
}
func TestBlobCallCloseFn(t *testing.T) {
var calls uint32
expected := errors.New("some close error")
b := &Blob{
closeFn: func() error {
atomic.AddUint32(&calls, 1)
return expected
},
}
got := b.Close()
assert.Equal(t, expected, got)
assert.EqualValues(t, 1, calls)
}
func TestBlobCanCloseWithoutCloseFn(t *testing.T) {
b := &Blob{
closeFn: nil,
}
assert.Nil(t, b.Close())
}
func TestBlobEqualReturnsTrueWithUnchangedContents(t *testing.T) {
c := strings.NewReader("Hello, world!")
b1 := &Blob{Size: int64(c.Len()), Contents: c}
b2 := &Blob{Size: int64(c.Len()), Contents: c}
assert.True(t, b1.Equal(b2))
}
func TestBlobEqualReturnsFalseWithChangedContents(t *testing.T) {
c1 := strings.NewReader("Hello, world!")
c2 := strings.NewReader("Goodbye, world!")
b1 := &Blob{Size: int64(c1.Len()), Contents: c1}
b2 := &Blob{Size: int64(c2.Len()), Contents: c2}
assert.False(t, b1.Equal(b2))
}
func TestBlobEqualReturnsTrueWhenOneBlobIsNil(t *testing.T) {
b1 := &Blob{Size: 1, Contents: bytes.NewReader([]byte{0xa})}
b2 := (*Blob)(nil)
assert.False(t, b1.Equal(b2))
assert.False(t, b2.Equal(b1))
}
func TestBlobEqualReturnsTrueWhenBothBlobsAreNil(t *testing.T) {
b1 := (*Blob)(nil)
b2 := (*Blob)(nil)
assert.True(t, b1.Equal(b2))
}

@ -1,324 +0,0 @@
package gitobj
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCommitReturnsCorrectObjectType(t *testing.T) {
assert.Equal(t, CommitObjectType, new(Commit).Type())
}
func TestCommitEncoding(t *testing.T) {
author := &Signature{Name: "John Doe", Email: "john@example.com", When: time.Now()}
committer := &Signature{Name: "Jane Doe", Email: "jane@example.com", When: time.Now()}
c := &Commit{
Author: author.String(),
Committer: committer.String(),
ParentIDs: [][]byte{
[]byte("aaaaaaaaaaaaaaaaaaaa"), []byte("bbbbbbbbbbbbbbbbbbbb"),
},
TreeID: []byte("cccccccccccccccccccc"),
ExtraHeaders: []*ExtraHeader{
{"foo", "bar"},
},
Message: "initial commit",
}
buf := new(bytes.Buffer)
_, err := c.Encode(buf)
assert.Nil(t, err)
assertLine(t, buf, "tree 6363636363636363636363636363636363636363")
assertLine(t, buf, "parent 6161616161616161616161616161616161616161")
assertLine(t, buf, "parent 6262626262626262626262626262626262626262")
assertLine(t, buf, "author %s", author.String())
assertLine(t, buf, "committer %s", committer.String())
assertLine(t, buf, "foo bar")
assertLine(t, buf, "")
assertLine(t, buf, "initial commit")
assert.Equal(t, 0, buf.Len())
}
func TestCommitDecoding(t *testing.T) {
author := &Signature{Name: "John Doe", Email: "john@example.com", When: time.Now()}
committer := &Signature{Name: "Jane Doe", Email: "jane@example.com", When: time.Now()}
p1 := []byte("aaaaaaaaaaaaaaaaaaaa")
p2 := []byte("bbbbbbbbbbbbbbbbbbbb")
treeId := []byte("cccccccccccccccccccc")
from := new(bytes.Buffer)
fmt.Fprintf(from, "author %s\n", author)
fmt.Fprintf(from, "committer %s\n", committer)
fmt.Fprintf(from, "parent %s\n", hex.EncodeToString(p1))
fmt.Fprintf(from, "parent %s\n", hex.EncodeToString(p2))
fmt.Fprintf(from, "foo bar\n")
fmt.Fprintf(from, "tree %s\n", hex.EncodeToString(treeId))
fmt.Fprintf(from, "\ninitial commit\n")
flen := from.Len()
commit := new(Commit)
n, err := commit.Decode(from, int64(flen))
assert.Nil(t, err)
assert.Equal(t, flen, n)
assert.Equal(t, author.String(), commit.Author)
assert.Equal(t, committer.String(), commit.Committer)
assert.Equal(t, [][]byte{p1, p2}, commit.ParentIDs)
assert.Equal(t, 1, len(commit.ExtraHeaders))
assert.Equal(t, "foo", commit.ExtraHeaders[0].K)
assert.Equal(t, "bar", commit.ExtraHeaders[0].V)
assert.Equal(t, "initial commit", commit.Message)
}
func TestCommitDecodingWithEmptyName(t *testing.T) {
author := &Signature{Name: "", Email: "john@example.com", When: time.Now()}
committer := &Signature{Name: "", Email: "jane@example.com", When: time.Now()}
treeId := []byte("cccccccccccccccccccc")
from := new(bytes.Buffer)
fmt.Fprintf(from, "author %s\n", author)
fmt.Fprintf(from, "committer %s\n", committer)
fmt.Fprintf(from, "tree %s\n", hex.EncodeToString(treeId))
fmt.Fprintf(from, "\ninitial commit\n")
flen := from.Len()
commit := new(Commit)
n, err := commit.Decode(from, int64(flen))
assert.Nil(t, err)
assert.Equal(t, flen, n)
assert.Equal(t, author.String(), commit.Author)
assert.Equal(t, committer.String(), commit.Committer)
assert.Equal(t, "initial commit", commit.Message)
}
func TestCommitDecodingWithMessageKeywordPrefix(t *testing.T) {
author := &Signature{Name: "John Doe", Email: "john@example.com", When: time.Now()}
committer := &Signature{Name: "Jane Doe", Email: "jane@example.com", When: time.Now()}
treeId := []byte("aaaaaaaaaaaaaaaaaaaa")
treeIdAscii := hex.EncodeToString(treeId)
from := new(bytes.Buffer)
fmt.Fprintf(from, "author %s\n", author)
fmt.Fprintf(from, "committer %s\n", committer)
fmt.Fprintf(from, "tree %s\n", hex.EncodeToString(treeId))
fmt.Fprintf(from, "\nfirst line\n\nsecond line\n")
flen := from.Len()
commit := new(Commit)
n, err := commit.Decode(from, int64(flen))
assert.NoError(t, err)
assert.Equal(t, flen, n)
assert.Equal(t, author.String(), commit.Author)
assert.Equal(t, committer.String(), commit.Committer)
assert.Equal(t, treeIdAscii, hex.EncodeToString(commit.TreeID))
assert.Equal(t, "first line\n\nsecond line", commit.Message)
}
func TestCommitDecodingWithWhitespace(t *testing.T) {
author := &Signature{Name: "John Doe", Email: "john@example.com", When: time.Now()}
committer := &Signature{Name: "Jane Doe", Email: "jane@example.com", When: time.Now()}
treeId := []byte("aaaaaaaaaaaaaaaaaaaa")
treeIdAscii := hex.EncodeToString(treeId)
from := new(bytes.Buffer)
fmt.Fprintf(from, "author %s\n", author)
fmt.Fprintf(from, "committer %s\n", committer)
fmt.Fprintf(from, "tree %s\n", hex.EncodeToString(treeId))
fmt.Fprintf(from, "\ntree <- initial commit\n")
flen := from.Len()
commit := new(Commit)
n, err := commit.Decode(from, int64(flen))
assert.NoError(t, err)
assert.Equal(t, flen, n)
assert.Equal(t, author.String(), commit.Author)
assert.Equal(t, committer.String(), commit.Committer)
assert.Equal(t, treeIdAscii, hex.EncodeToString(commit.TreeID))
assert.Equal(t, "tree <- initial commit", commit.Message)
}
func assertLine(t *testing.T, buf *bytes.Buffer, wanted string, args ...interface{}) {
got, err := buf.ReadString('\n')
if err == io.EOF {
err = nil
}
assert.Nil(t, err)
assert.Equal(t, fmt.Sprintf(wanted, args...), strings.TrimSuffix(got, "\n"))
}
func TestCommitEqualReturnsTrueWithIdenticalCommits(t *testing.T) {
c1 := &Commit{
Author: "Jane Doe <jane@example.com> 1503956287 -0400",
Committer: "Jane Doe <jane@example.com> 1503956287 -0400",
ParentIDs: [][]byte{make([]byte, 20)},
TreeID: make([]byte, 20),
ExtraHeaders: []*ExtraHeader{
{K: "Signed-off-by", V: "Joe Smith"},
},
Message: "initial commit",
}
c2 := &Commit{
Author: "Jane Doe <jane@example.com> 1503956287 -0400",
Committer: "Jane Doe <jane@example.com> 1503956287 -0400",
ParentIDs: [][]byte{make([]byte, 20)},
TreeID: make([]byte, 20),
ExtraHeaders: []*ExtraHeader{
{K: "Signed-off-by", V: "Joe Smith"},
},
Message: "initial commit",
}
assert.True(t, c1.Equal(c2))
}
func TestCommitEqualReturnsFalseWithDifferentParentCounts(t *testing.T) {
c1 := &Commit{
ParentIDs: [][]byte{make([]byte, 20), make([]byte, 20)},
}
c2 := &Commit{
ParentIDs: [][]byte{make([]byte, 20)},
}
assert.False(t, c1.Equal(c2))
}
func TestCommitEqualReturnsFalseWithDifferentParentsIds(t *testing.T) {
c1 := &Commit{
ParentIDs: [][]byte{make([]byte, 20)},
}
c2 := &Commit{
ParentIDs: [][]byte{make([]byte, 20)},
}
c1.ParentIDs[0][1] = 0x1
assert.False(t, c1.Equal(c2))
}
func TestCommitEqualReturnsFalseWithDifferentHeaderCounts(t *testing.T) {
c1 := &Commit{
ExtraHeaders: []*ExtraHeader{
{K: "Signed-off-by", V: "Joe Smith"},
{K: "GPG-Signature", V: "..."},
},
}
c2 := &Commit{
ExtraHeaders: []*ExtraHeader{
{K: "Signed-off-by", V: "Joe Smith"},
},
}
assert.False(t, c1.Equal(c2))
}
func TestCommitEqualReturnsFalseWithDifferentHeaders(t *testing.T) {
c1 := &Commit{
ExtraHeaders: []*ExtraHeader{
{K: "Signed-off-by", V: "Joe Smith"},
},
}
c2 := &Commit{
ExtraHeaders: []*ExtraHeader{
{K: "Signed-off-by", V: "Jane Smith"},
},
}
assert.False(t, c1.Equal(c2))
}
func TestCommitEqualReturnsFalseWithDifferentAuthors(t *testing.T) {
c1 := &Commit{
Author: "Jane Doe <jane@example.com> 1503956287 -0400",
}
c2 := &Commit{
Author: "John Doe <john@example.com> 1503956287 -0400",
}
assert.False(t, c1.Equal(c2))
}
func TestCommitEqualReturnsFalseWithDifferentCommitters(t *testing.T) {
c1 := &Commit{
Committer: "Jane Doe <jane@example.com> 1503956287 -0400",
}
c2 := &Commit{
Committer: "John Doe <john@example.com> 1503956287 -0400",
}
assert.False(t, c1.Equal(c2))
}
func TestCommitEqualReturnsFalseWithDifferentMessages(t *testing.T) {
c1 := &Commit{
Message: "initial commit",
}
c2 := &Commit{
Message: "not the initial commit",
}
assert.False(t, c1.Equal(c2))
}
func TestCommitEqualReturnsFalseWithDifferentTreeIDs(t *testing.T) {
c1 := &Commit{
TreeID: make([]byte, 20),
}
c2 := &Commit{
TreeID: make([]byte, 20),
}
c1.TreeID[0] = 0x1
assert.False(t, c1.Equal(c2))
}
func TestCommitEqualReturnsFalseWhenOneCommitIsNil(t *testing.T) {
c1 := &Commit{
Author: "Jane Doe <jane@example.com> 1503956287 -0400",
Committer: "Jane Doe <jane@example.com> 1503956287 -0400",
ParentIDs: [][]byte{make([]byte, 20)},
TreeID: make([]byte, 20),
ExtraHeaders: []*ExtraHeader{
{K: "Signed-off-by", V: "Joe Smith"},
},
Message: "initial commit",
}
c2 := (*Commit)(nil)
assert.False(t, c1.Equal(c2))
}
func TestCommitEqualReturnsTrueWhenBothCommitsAreNil(t *testing.T) {
c1 := (*Commit)(nil)
c2 := (*Commit)(nil)
assert.True(t, c1.Equal(c2))
}

@ -1,15 +0,0 @@
package gitobj
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnexpectedObjectTypeErrFormatting(t *testing.T) {
err := &UnexpectedObjectType{
Got: TreeObjectType, Wanted: BlobObjectType,
}
assert.Equal(t, "gitobj: unexpected object type, got: \"tree\", wanted: \"blob\"", err.Error())
}

@ -1,88 +0,0 @@
package gitobj
import (
"bytes"
"encoding/hex"
"io"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMemoryStorerIncludesGivenEntries(t *testing.T) {
sha := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
hex, err := hex.DecodeString(sha)
assert.Nil(t, err)
ms := newMemoryStorer(map[string]io.ReadWriter{
sha: bytes.NewBuffer([]byte{0x1}),
})
buf, err := ms.Open(hex)
assert.Nil(t, err)
contents, err := ioutil.ReadAll(buf)
assert.Nil(t, err)
assert.Equal(t, []byte{0x1}, contents)
}
func TestMemoryStorerAcceptsNilEntries(t *testing.T) {
ms := newMemoryStorer(nil)
assert.NotNil(t, ms)
assert.Equal(t, 0, len(ms.fs))
}
func TestMemoryStorerDoesntOpenMissingEntries(t *testing.T) {
sha := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
hex, err := hex.DecodeString(sha)
assert.Nil(t, err)
ms := newMemoryStorer(nil)
f, err := ms.Open(hex)
assert.Equal(t, os.ErrNotExist, err)
assert.Nil(t, f)
}
func TestMemoryStorerStoresNewEntries(t *testing.T) {
hex, err := hex.DecodeString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
assert.Nil(t, err)
ms := newMemoryStorer(nil)
assert.Equal(t, 0, len(ms.fs))
_, err = ms.Store(hex, strings.NewReader("hello"))
assert.Nil(t, err)
assert.Equal(t, 1, len(ms.fs))
got, err := ms.Open(hex)
assert.Nil(t, err)
contents, err := ioutil.ReadAll(got)
assert.Nil(t, err)
assert.Equal(t, "hello", string(contents))
}
func TestMemoryStorerStoresExistingEntries(t *testing.T) {
hex, err := hex.DecodeString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
assert.Nil(t, err)
ms := newMemoryStorer(nil)
assert.Equal(t, 0, len(ms.fs))
_, err = ms.Store(hex, new(bytes.Buffer))
assert.Nil(t, err)
assert.Equal(t, 1, len(ms.fs))
n, err := ms.Store(hex, new(bytes.Buffer))
assert.Nil(t, err)
assert.EqualValues(t, 0, n)
}

@ -1,255 +0,0 @@
package gitobj
import (
"bytes"
"compress/zlib"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDecodeBlob(t *testing.T) {
sha := "af5626b4a114abcb82d63db7c8082c3c4756e51b"
contents := "Hello, world!\n"
var buf bytes.Buffer
zw := zlib.NewWriter(&buf)
fmt.Fprintf(zw, "blob 14\x00%s", contents)
zw.Close()
odb := &ObjectDatabase{s: newMemoryStorer(map[string]io.ReadWriter{
sha: &buf,
})}
shaHex, _ := hex.DecodeString(sha)
blob, err := odb.Blob(shaHex)
assert.Nil(t, err)
assert.EqualValues(t, 14, blob.Size)
got, err := ioutil.ReadAll(blob.Contents)
assert.Nil(t, err)
assert.Equal(t, contents, string(got))
}
func TestDecodeTree(t *testing.T) {
sha := "fcb545d5746547a597811b7441ed8eba307be1ff"
hexSha, err := hex.DecodeString(sha)
require.Nil(t, err)
blobSha := "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
hexBlobSha, err := hex.DecodeString(blobSha)
require.Nil(t, err)
var buf bytes.Buffer
zw := zlib.NewWriter(&buf)
fmt.Fprintf(zw, "tree 37\x00")
fmt.Fprintf(zw, "100644 hello.txt\x00")
zw.Write(hexBlobSha)
zw.Close()
odb := &ObjectDatabase{s: newMemoryStorer(map[string]io.ReadWriter{
sha: &buf,
})}
tree, err := odb.Tree(hexSha)
assert.Nil(t, err)
require.Equal(t, 1, len(tree.Entries))
assert.Equal(t, &TreeEntry{
Name: "hello.txt",
Oid: hexBlobSha,
Filemode: 0100644,
}, tree.Entries[0])
}
func TestDecodeCommit(t *testing.T) {
sha := "d7283480bb6dc90be621252e1001a93871dcf511"
commitShaHex, err := hex.DecodeString(sha)
assert.Nil(t, err)
var buf bytes.Buffer
zw := zlib.NewWriter(&buf)
fmt.Fprintf(zw, "commit 173\x00")
fmt.Fprintf(zw, "tree fcb545d5746547a597811b7441ed8eba307be1ff\n")
fmt.Fprintf(zw, "author Taylor Blau <me@ttaylorr.com> 1494620424 -0600\n")
fmt.Fprintf(zw, "committer Taylor Blau <me@ttaylorr.com> 1494620424 -0600\n")
fmt.Fprintf(zw, "\ninitial commit\n")
zw.Close()
odb := &ObjectDatabase{s: newMemoryStorer(map[string]io.ReadWriter{
sha: &buf,
})}
commit, err := odb.Commit(commitShaHex)
assert.Nil(t, err)
assert.Equal(t, "Taylor Blau <me@ttaylorr.com> 1494620424 -0600", commit.Author)
assert.Equal(t, "Taylor Blau <me@ttaylorr.com> 1494620424 -0600", commit.Committer)
assert.Equal(t, "initial commit", commit.Message)
assert.Equal(t, 0, len(commit.ParentIDs))
assert.Equal(t, "fcb545d5746547a597811b7441ed8eba307be1ff", hex.EncodeToString(commit.TreeID))
}
func TestWriteBlob(t *testing.T) {
fs := newMemoryStorer(make(map[string]io.ReadWriter))
odb := &ObjectDatabase{s: fs}
sha, err := odb.WriteBlob(&Blob{
Size: 14,
Contents: strings.NewReader("Hello, world!\n"),
})
expected := "af5626b4a114abcb82d63db7c8082c3c4756e51b"
assert.Nil(t, err)
assert.Equal(t, expected, hex.EncodeToString(sha))
assert.NotNil(t, fs.fs[hex.EncodeToString(sha)])
}
func TestWriteTree(t *testing.T) {
fs := newMemoryStorer(make(map[string]io.ReadWriter))
odb := &ObjectDatabase{s: fs}
blobSha := "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
hexBlobSha, err := hex.DecodeString(blobSha)
require.Nil(t, err)
sha, err := odb.WriteTree(&Tree{Entries: []*TreeEntry{
{
Name: "hello.txt",
Oid: hexBlobSha,
Filemode: 0100644,
},
}})
expected := "fcb545d5746547a597811b7441ed8eba307be1ff"
assert.Nil(t, err)
assert.Equal(t, expected, hex.EncodeToString(sha))
assert.NotNil(t, fs.fs[hex.EncodeToString(sha)])
}
func TestWriteCommit(t *testing.T) {
fs := newMemoryStorer(make(map[string]io.ReadWriter))
odb := &ObjectDatabase{s: fs}
when := time.Unix(1257894000, 0).UTC()
author := &Signature{Name: "John Doe", Email: "john@example.com", When: when}
committer := &Signature{Name: "Jane Doe", Email: "jane@example.com", When: when}
tree := "fcb545d5746547a597811b7441ed8eba307be1ff"
treeHex, err := hex.DecodeString(tree)
assert.Nil(t, err)
sha, err := odb.WriteCommit(&Commit{
Author: author.String(),
Committer: committer.String(),
TreeID: treeHex,
Message: "initial commit",
})
expected := "fee8a35c2890cd6e0e28d24cc457fcecbd460962"
assert.Nil(t, err)
assert.Equal(t, expected, hex.EncodeToString(sha))
assert.NotNil(t, fs.fs[hex.EncodeToString(sha)])
}
func TestDecodeTag(t *testing.T) {
const sha = "7639ba293cd2c457070e8446ecdea56682af0f48"
tagShaHex, err := hex.DecodeString(sha)
var buf bytes.Buffer
zw := zlib.NewWriter(&buf)
fmt.Fprintf(zw, "tag 165\x00")
fmt.Fprintf(zw, "object 6161616161616161616161616161616161616161\n")
fmt.Fprintf(zw, "type commit\n")
fmt.Fprintf(zw, "tag v2.4.0\n")
fmt.Fprintf(zw, "tagger A U Thor <author@example.com>\n")
fmt.Fprintf(zw, "\n")
fmt.Fprintf(zw, "The quick brown fox jumps over the lazy dog.\n")
zw.Close()
odb := &ObjectDatabase{s: newMemoryStorer(map[string]io.ReadWriter{
sha: &buf,
})}
tag, err := odb.Tag(tagShaHex)
assert.Nil(t, err)
assert.Equal(t, []byte("aaaaaaaaaaaaaaaaaaaa"), tag.Object)
assert.Equal(t, CommitObjectType, tag.ObjectType)
assert.Equal(t, "v2.4.0", tag.Name)
assert.Equal(t, "A U Thor <author@example.com>", tag.Tagger)
assert.Equal(t, "The quick brown fox jumps over the lazy dog.", tag.Message)
}
func TestWriteTag(t *testing.T) {
fs := newMemoryStorer(make(map[string]io.ReadWriter))
odb := &ObjectDatabase{s: fs}
sha, err := odb.WriteTag(&Tag{
Object: []byte("aaaaaaaaaaaaaaaaaaaa"),
ObjectType: CommitObjectType,
Name: "v2.4.0",
Tagger: "A U Thor <author@example.com>",
Message: "The quick brown fox jumps over the lazy dog.",
})
expected := "e614dda21829f4176d3db27fe62fb4aee2e2475d"
assert.Nil(t, err)
assert.Equal(t, expected, hex.EncodeToString(sha))
assert.NotNil(t, fs.fs[hex.EncodeToString(sha)])
}
func TestReadingAMissingObjectAfterClose(t *testing.T) {
sha, _ := hex.DecodeString("af5626b4a114abcb82d63db7c8082c3c4756e51b")
db := &ObjectDatabase{
s: newMemoryStorer(nil),
closed: 1,
}
blob, err := db.Blob(sha)
assert.EqualError(t, err, "gitobj: cannot use closed *pack.Set")
assert.Nil(t, blob)
}
func TestClosingAnObjectDatabaseMoreThanOnce(t *testing.T) {
db, err := FromFilesystem("/tmp", "")
assert.Nil(t, err)
assert.Nil(t, db.Close())
assert.EqualError(t, db.Close(), "gitobj: *ObjectDatabase already closed")
}
func TestObjectDatabaseRootWithRoot(t *testing.T) {
db, err := FromFilesystem("/foo/bar/baz", "")
assert.Nil(t, err)
root, ok := db.Root()
assert.Equal(t, "/foo/bar/baz", root)
assert.True(t, ok)
}
func TestObjectDatabaseRootWithoutRoot(t *testing.T) {
root, ok := new(ObjectDatabase).Root()
assert.Equal(t, "", root)
assert.False(t, ok)
}

@ -1,76 +0,0 @@
package gitobj
import (
"bytes"
"compress/zlib"
"errors"
"io"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestObjectReaderReadsHeaders(t *testing.T) {
var compressed bytes.Buffer
zw := zlib.NewWriter(&compressed)
zw.Write([]byte("blob 1\x00"))
zw.Close()
or, err := NewObjectReader(&compressed)
assert.Nil(t, err)
typ, size, err := or.Header()
assert.Nil(t, err)
assert.EqualValues(t, 1, size)
assert.Equal(t, BlobObjectType, typ)
}
func TestObjectReaderConsumesHeaderBeforeReads(t *testing.T) {
var compressed bytes.Buffer
zw := zlib.NewWriter(&compressed)
zw.Write([]byte("blob 1\x00asdf"))
zw.Close()
or, err := NewObjectReader(&compressed)
assert.Nil(t, err)
var buf [4]byte
n, err := or.Read(buf[:])
assert.Equal(t, 4, n)
assert.Equal(t, []byte{'a', 's', 'd', 'f'}, buf[:])
assert.Nil(t, err)
}
type ReadCloserFn struct {
io.Reader
closeFn func() error
}
func (r *ReadCloserFn) Close() error {
return r.closeFn()
}
func TestObjectReaderCallsClose(t *testing.T) {
var calls uint32
expected := errors.New("expected")
or, err := NewObjectReadCloser(&ReadCloserFn{
Reader: bytes.NewBuffer([]byte{0x78, 0x01}),
closeFn: func() error {
atomic.AddUint32(&calls, 1)
return expected
},
})
assert.Nil(t, err)
got := or.Close()
assert.Equal(t, expected, got)
assert.EqualValues(t, 1, atomic.LoadUint32(&calls))
}

@ -1,37 +0,0 @@
package gitobj
import (
"math"
"testing"
"github.com/stretchr/testify/assert"
)
func TestObjectTypeFromString(t *testing.T) {
for str, typ := range map[string]ObjectType{
"blob": BlobObjectType,
"tree": TreeObjectType,
"commit": CommitObjectType,
"tag": TagObjectType,
"something else": UnknownObjectType,
} {
t.Run(str, func(t *testing.T) {
assert.Equal(t, typ, ObjectTypeFromString(str))
})
}
}
func TestObjectTypeToString(t *testing.T) {
for typ, str := range map[ObjectType]string{
BlobObjectType: "blob",
TreeObjectType: "tree",
CommitObjectType: "commit",
TagObjectType: "tag",
UnknownObjectType: "unknown",
ObjectType(math.MaxUint8): "<unknown>",
} {
t.Run(str, func(t *testing.T) {
assert.Equal(t, str, typ.String())
})
}
}

@ -1,118 +0,0 @@
package gitobj
import (
"bytes"
"compress/zlib"
"encoding/hex"
"errors"
"io"
"io/ioutil"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestObjectWriterWritesHeaders(t *testing.T) {
var buf bytes.Buffer
w := NewObjectWriter(&buf)
n, err := w.WriteHeader(BlobObjectType, 1)
assert.Equal(t, 7, n)
assert.Nil(t, err)
assert.Nil(t, w.Close())
r, err := zlib.NewReader(&buf)
assert.Nil(t, err)
all, err := ioutil.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, []byte("blob 1\x00"), all)
assert.Nil(t, r.Close())
}
func TestObjectWriterWritesData(t *testing.T) {
var buf bytes.Buffer
w := NewObjectWriter(&buf)
w.WriteHeader(BlobObjectType, 1)
n, err := w.Write([]byte{0x31})
assert.Equal(t, 1, n)
assert.Nil(t, err)
assert.Nil(t, w.Close())
r, err := zlib.NewReader(&buf)
assert.Nil(t, err)
all, err := ioutil.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, []byte("blob 1\x001"), all)
assert.Nil(t, r.Close())
}
func TestObjectWriterPanicsOnWritesWithoutHeader(t *testing.T) {
defer func() {
err := recover()
assert.NotNil(t, err)
assert.Equal(t, "gitobj: cannot write data without header", err)
}()
w := NewObjectWriter(new(bytes.Buffer))
w.Write(nil)
}
func TestObjectWriterPanicsOnMultipleHeaderWrites(t *testing.T) {
defer func() {
err := recover()
assert.NotNil(t, err)
assert.Equal(t, "gitobj: cannot write headers more than once", err)
}()
w := NewObjectWriter(new(bytes.Buffer))
w.WriteHeader(BlobObjectType, 1)
w.WriteHeader(TreeObjectType, 2)
}
func TestObjectWriterKeepsTrackOfHash(t *testing.T) {
w := NewObjectWriter(new(bytes.Buffer))
n, err := w.WriteHeader(BlobObjectType, 1)
assert.Nil(t, err)
assert.Equal(t, 7, n)
assert.Equal(t, "bb6ca78b66403a67c6281df142de5ef472186283", hex.EncodeToString(w.Sha()))
}
type WriteCloserFn struct {
io.Writer
closeFn func() error
}
func (r *WriteCloserFn) Close() error { return r.closeFn() }
func TestObjectWriterCallsClose(t *testing.T) {
var calls uint32
expected := errors.New("close error")
w := NewObjectWriteCloser(&WriteCloserFn{
Writer: new(bytes.Buffer),
closeFn: func() error {
atomic.AddUint32(&calls, 1)
return expected
},
})
got := w.Close()
assert.EqualValues(t, 1, calls)
assert.Equal(t, expected, got)
}

@ -1,78 +0,0 @@
package pack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBoundsLeft(t *testing.T) {
assert.EqualValues(t, 1, newBounds(1, 2).Left())
}
func TestBoundsRight(t *testing.T) {
assert.EqualValues(t, 2, newBounds(1, 2).Right())
}
func TestBoundsWithLeftReturnsNewBounds(t *testing.T) {
b1 := newBounds(1, 2)
b2 := b1.WithLeft(3)
assert.EqualValues(t, 1, b1.Left())
assert.EqualValues(t, 2, b1.Right())
assert.EqualValues(t, 3, b2.Left())
assert.EqualValues(t, 2, b2.Right())
}
func TestBoundsWithRightReturnsNewBounds(t *testing.T) {
b1 := newBounds(1, 2)
b2 := b1.WithRight(3)
assert.EqualValues(t, 1, b1.Left())
assert.EqualValues(t, 2, b1.Right())
assert.EqualValues(t, 1, b2.Left())
assert.EqualValues(t, 3, b2.Right())
}
func TestBoundsEqualWithIdenticalBounds(t *testing.T) {
b1 := newBounds(1, 2)
b2 := newBounds(1, 2)
assert.True(t, b1.Equal(b2))
}
func TestBoundsEqualWithDifferentBounds(t *testing.T) {
b1 := newBounds(1, 2)
b2 := newBounds(3, 4)
assert.False(t, b1.Equal(b2))
}
func TestBoundsEqualWithNilReceiver(t *testing.T) {
bnil := (*bounds)(nil)
b2 := newBounds(1, 2)
assert.False(t, bnil.Equal(b2))
}
func TestBoundsEqualWithNilArgument(t *testing.T) {
b1 := newBounds(1, 2)
bnil := (*bounds)(nil)
assert.False(t, b1.Equal(bnil))
}
func TestBoundsEqualWithNilArgumentAndReceiver(t *testing.T) {
b1 := (*bounds)(nil)
b2 := (*bounds)(nil)
assert.True(t, b1.Equal(b2))
}
func TestBoundsString(t *testing.T) {
b1 := newBounds(1, 2)
assert.Equal(t, "[1,2]", b1.String())
}

@ -1,60 +0,0 @@
package pack
import (
"bytes"
"compress/zlib"
"testing"
"github.com/stretchr/testify/assert"
)
func TestChainBaseDecompressesData(t *testing.T) {
const contents = "Hello, world!\n"
compressed, err := compress(contents)
assert.NoError(t, err)
var buf bytes.Buffer
_, err = buf.Write([]byte{0x0, 0x0, 0x0, 0x0})
assert.NoError(t, err)
_, err = buf.Write(compressed)
assert.NoError(t, err)
_, err = buf.Write([]byte{0x0, 0x0, 0x0, 0x0})
assert.NoError(t, err)
base := &ChainBase{
offset: 4,
size: int64(len(contents)),
r: bytes.NewReader(buf.Bytes()),
}
unpacked, err := base.Unpack()
assert.NoError(t, err)
assert.Equal(t, contents, string(unpacked))
}
func TestChainBaseTypeReturnsType(t *testing.T) {
b := &ChainBase{
typ: TypeCommit,
}
assert.Equal(t, TypeCommit, b.Type())
}
func compress(base string) ([]byte, error) {
var buf bytes.Buffer
zw := zlib.NewWriter(&buf)
if _, err := zw.Write([]byte(base)); err != nil {
return nil, err
}
if err := zw.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

@ -1,112 +0,0 @@
package pack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestChainDeltaUnpackCopiesFromBase(t *testing.T) {
c := &ChainDelta{
base: &ChainSimple{
X: []byte{0x0, 0x1, 0x2, 0x3},
},
delta: []byte{
0x04, // Source size: 4.
0x03, // Destination size: 3.
0x80 | 0x01 | 0x10, // Copy, omask=0001, smask=0001.
0x1, // Offset: 1.
0x3, // Size: 3.
},
}
data, err := c.Unpack()
assert.NoError(t, err)
assert.Equal(t, []byte{0x1, 0x2, 0x3}, data)
}
func TestChainDeltaUnpackAddsToBase(t *testing.T) {
c := &ChainDelta{
base: &ChainSimple{
X: make([]byte, 0),
},
delta: []byte{
0x0, // Source size: 0.
0x3, // Destination size: 3.
0x3, // Add, size=3.
0x1, 0x2, 0x3, // Contents: ...
},
}
data, err := c.Unpack()
assert.NoError(t, err)
assert.Equal(t, []byte{0x1, 0x2, 0x3}, data)
}
func TestChainDeltaWithMultipleInstructions(t *testing.T) {
c := &ChainDelta{
base: &ChainSimple{
X: []byte{'H', 'e', 'l', 'l', 'o', '!', '\n'},
},
delta: []byte{
0x07, // Source size: 7.
0x0e, // Destination size: 14.
0x80 | 0x01 | 0x10, // Copy, omask=0001, smask=0001.
0x0, // Offset: 1.
0x5, // Size: 5.
0x7, // Add, size=7.
',', ' ', 'w', 'o', 'r', 'l', 'd', // Contents: ...
0x80 | 0x01 | 0x10, // Copy, omask=0001, smask=0001.
0x05, // Offset: 5.
0x02, // Size: 2.
},
}
data, err := c.Unpack()
assert.NoError(t, err)
assert.Equal(t, []byte("Hello, world!\n"), data)
}
func TestChainDeltaWithInvalidDeltaInstruction(t *testing.T) {
c := &ChainDelta{
base: &ChainSimple{
X: make([]byte, 0),
},
delta: []byte{
0x0, // Source size: 0.
0x1, // Destination size: 3.
0x0, // Invalid instruction.
},
}
data, err := c.Unpack()
assert.EqualError(t, err, "gitobj/pack: invalid delta data")
assert.Nil(t, data)
}
func TestChainDeltaWithExtraInstructions(t *testing.T) {
c := &ChainDelta{
base: &ChainSimple{
X: make([]byte, 0),
},
delta: []byte{
0x0, // Source size: 0.
0x3, // Destination size: 3.
0x4, // Add, size=4 (invalid).
0x1, 0x2, 0x3, 0x4, // Contents: ...
},
}
data, err := c.Unpack()
assert.EqualError(t, err, "gitobj/pack: invalid delta data")
assert.Nil(t, data)
}

@ -1,12 +0,0 @@
package pack
type ChainSimple struct {
X []byte
Err error
}
func (c *ChainSimple) Unpack() ([]byte, error) {
return c.X, c.Err
}
func (c *ChainSimple) Type() PackedObjectType { return TypeNone }

@ -1,13 +0,0 @@
package pack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnsupportedVersionErr(t *testing.T) {
u := &UnsupportedVersionErr{Got: 3}
assert.Error(t, u, "gitobj/pack: unsupported version: 3")
}

@ -1,72 +0,0 @@
package pack
import (
"bytes"
"encoding/binary"
"io"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDecodeIndexV2(t *testing.T) {
buf := make([]byte, 0, indexV2Width+indexFanoutWidth)
buf = append(buf, 0xff, 0x74, 0x4f, 0x63)
buf = append(buf, 0x0, 0x0, 0x0, 0x2)
for i := 0; i < indexFanoutEntries; i++ {
x := make([]byte, 4)
binary.BigEndian.PutUint32(x, uint32(3))
buf = append(buf, x...)
}
idx, err := DecodeIndex(bytes.NewReader(buf))
assert.NoError(t, err)
assert.EqualValues(t, 3, idx.Count())
}
func TestDecodeIndexV2InvalidFanout(t *testing.T) {
buf := make([]byte, 0, indexV2Width+indexFanoutWidth-indexFanoutEntryWidth)
buf = append(buf, 0xff, 0x74, 0x4f, 0x63)
buf = append(buf, 0x0, 0x0, 0x0, 0x2)
buf = append(buf, make([]byte, indexFanoutWidth-1)...)
idx, err := DecodeIndex(bytes.NewReader(buf))
assert.Equal(t, ErrShortFanout, err)
assert.Nil(t, idx)
}
func TestDecodeIndexV1(t *testing.T) {
idx, err := DecodeIndex(bytes.NewReader(make([]byte, indexFanoutWidth)))
assert.NoError(t, err)
assert.EqualValues(t, 0, idx.Count())
}
func TestDecodeIndexV1InvalidFanout(t *testing.T) {
idx, err := DecodeIndex(bytes.NewReader(make([]byte, indexFanoutWidth-1)))
assert.Equal(t, ErrShortFanout, err)
assert.Nil(t, idx)
}
func TestDecodeIndexUnsupportedVersion(t *testing.T) {
buf := make([]byte, 0, 4+4)
buf = append(buf, 0xff, 0x74, 0x4f, 0x63)
buf = append(buf, 0x0, 0x0, 0x0, 0x3)
idx, err := DecodeIndex(bytes.NewReader(buf))
assert.EqualError(t, err, "gitobj/pack: unsupported version: 3")
assert.Nil(t, idx)
}
func TestDecodeIndexEmptyContents(t *testing.T) {
idx, err := DecodeIndex(bytes.NewReader(make([]byte, 0)))
assert.Equal(t, io.EOF, err)
assert.Nil(t, idx)
}

@ -1,178 +0,0 @@
package pack
import (
"bytes"
"encoding/binary"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
var (
idx *Index
)
func TestIndexEntrySearch(t *testing.T) {
e, err := idx.Entry([]byte{
0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
})
assert.NoError(t, err)
assert.EqualValues(t, 6, e.PackOffset)
}
func TestIndexEntrySearchClampLeft(t *testing.T) {
e, err := idx.Entry([]byte{
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
})
assert.NoError(t, err)
assert.EqualValues(t, 0, e.PackOffset)
}
func TestIndexEntrySearchClampRight(t *testing.T) {
e, err := idx.Entry([]byte{
0xff, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
})
assert.NoError(t, err)
assert.EqualValues(t, 0x4ff, e.PackOffset)
}
func TestIndexSearchOutOfBounds(t *testing.T) {
e, err := idx.Entry([]byte{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
})
assert.True(t, IsNotFound(err), "expected err to be 'not found'")
assert.Nil(t, e)
}
func TestIndexEntryNotFound(t *testing.T) {
e, err := idx.Entry([]byte{
0x1, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6,
0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6,
})
assert.True(t, IsNotFound(err), "expected err to be 'not found'")
assert.Nil(t, e)
}
func TestIndexCount(t *testing.T) {
fanout := make([]uint32, 256)
for i := 0; i < len(fanout); i++ {
fanout[i] = uint32(i)
}
idx := &Index{fanout: fanout}
assert.EqualValues(t, 255, idx.Count())
}
func TestIndexIsNotFound(t *testing.T) {
assert.True(t, IsNotFound(errNotFound),
"expected 'errNotFound' to satisfy 'IsNotFound()'")
}
func TestIndexIsNotFoundForOtherErrors(t *testing.T) {
assert.False(t, IsNotFound(fmt.Errorf("gitobj/pack: misc")),
"expected 'err' not to satisfy 'IsNotFound()'")
}
// init generates some fixture data and then constructs an *Index instance using
// it.
func init() {
// eps is the number of SHA1 names generated under each 0x<t> slot.
const eps = 5
hdr := []byte{
0xff, 0x74, 0x4f, 0x63, // Index file v2+ magic header
0x00, 0x00, 0x00, 0x02, // 4-byte version indicator
}
// Create a fanout table using uint32s (later marshalled using
// binary.BigEndian).
//
// Since we have an even distribution of SHA1s in the generated index,
// each entry will increase by the number of entries per slot (see: eps
// above).
fanout := make([]uint32, indexFanoutEntries)
for i := 0; i < len(fanout); i++ {
// Begin the index at (i+1), since the fanout table mandates
// objects less than the value at index "i".
fanout[i] = uint32((i + 1) * eps)
}
offs := make([]uint32, 0, 256*eps)
crcs := make([]uint32, 0, 256*eps)
names := make([][]byte, 0, 256*eps)
for i := 0; i < 256; i++ {
// For each name, generate a unique SHA using the prefix "i",
// and then suffix "j".
//
// In other words, when i=1, we will generate:
// []byte{0x1 0x0 0x0 0x0 ...}
// []byte{0x1 0x1 0x1 0x1 ...}
// []byte{0x1 0x2 0x2 0x2 ...}
//
// and etc.
for j := 0; j < eps; j++ {
var sha [20]byte
sha[0] = byte(i)
for r := 1; r < len(sha); r++ {
sha[r] = byte(j)
}
cpy := make([]byte, len(sha))
copy(cpy, sha[:])
names = append(names, cpy)
offs = append(offs, uint32((i*eps)+j))
crcs = append(crcs, 0)
}
}
// Create a buffer to hold the index contents:
buf := bytes.NewBuffer(hdr)
// Write each value in the fanout table using a 32bit network byte-order
// integer.
for _, f := range fanout {
binary.Write(buf, binary.BigEndian, f)
}
// Write each SHA1 name to the table next.
for _, name := range names {
buf.Write(name)
}
// Then write each of the CRC values in network byte-order as a 32bit
// unsigned integer.
for _, crc := range crcs {
binary.Write(buf, binary.BigEndian, crc)
}
// Do the same with the offsets.
for _, off := range offs {
binary.Write(buf, binary.BigEndian, off)
}
idx = &Index{
fanout: fanout,
// version is unimportant here, use V2 since it's more common in
// the wild.
version: new(V2),
// *bytes.Buffer does not implement io.ReaderAt, but
// *bytes.Reader does.
//
// Call (*bytes.Buffer).Bytes() to get the data, and then
// construct a new *bytes.Reader with it to implement
// io.ReaderAt.
r: bytes.NewReader(buf.Bytes()),
}
}

@ -1,77 +0,0 @@
package pack
import (
"bytes"
"encoding/binary"
"testing"
"github.com/stretchr/testify/assert"
)
var (
V1IndexFanout = make([]uint32, indexFanoutEntries)
V1IndexSmallEntry = []byte{
0x0, 0x0, 0x0, 0x1,
0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
}
V1IndexSmallSha = V1IndexSmallEntry[4:]
V1IndexMediumEntry = []byte{
0x0, 0x0, 0x0, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
}
V1IndexMediumSha = V1IndexMediumEntry[4:]
V1IndexLargeEntry = []byte{
0x0, 0x0, 0x0, 0x3,
0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
}
V1IndexLargeSha = V1IndexLargeEntry[4:]
V1Index = &Index{
fanout: V1IndexFanout,
version: new(V1),
}
)
func TestIndexV1SearchExact(t *testing.T) {
e, err := new(V1).Entry(V1Index, 1)
assert.NoError(t, err)
assert.EqualValues(t, 2, e.PackOffset)
}
func TestIndexVersionWidthV1(t *testing.T) {
assert.EqualValues(t, 0, new(V1).Width())
}
func init() {
V1IndexFanout[1] = 1
V1IndexFanout[2] = 2
V1IndexFanout[3] = 3
for i := 3; i < len(V1IndexFanout); i++ {
V1IndexFanout[i] = 3
}
fanout := make([]byte, indexFanoutWidth)
for i, n := range V1IndexFanout {
binary.BigEndian.PutUint32(fanout[i*indexFanoutEntryWidth:], n)
}
buf := make([]byte, 0, indexOffsetV1Start+(3*indexObjectEntryV1Width))
buf = append(buf, fanout...)
buf = append(buf, V1IndexSmallEntry...)
buf = append(buf, V1IndexMediumEntry...)
buf = append(buf, V1IndexLargeEntry...)
V1Index.r = bytes.NewReader(buf)
}

@ -1,93 +0,0 @@
package pack
import (
"bytes"
"encoding/binary"
"testing"
"github.com/stretchr/testify/assert"
)
var (
V2IndexHeader = []byte{
0xff, 0x74, 0x4f, 0x63,
0x00, 0x00, 0x00, 0x02,
}
V2IndexFanout = make([]uint32, indexFanoutEntries)
V2IndexNames = []byte{
0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
}
V2IndexSmallSha = V2IndexNames[0:20]
V2IndexMediumSha = V2IndexNames[20:40]
V2IndexLargeSha = V2IndexNames[40:60]
V2IndexCRCs = []byte{
0x0, 0x0, 0x0, 0x0,
0x1, 0x1, 0x1, 0x1,
0x2, 0x2, 0x2, 0x2,
}
V2IndexOffsets = []byte{
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x02,
0x80, 0x00, 0x00, 0x01, // use the second large offset
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // filler data
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // large offset
}
V2Index = &Index{
fanout: V2IndexFanout,
version: new(V2),
}
)
func TestIndexV2EntryExact(t *testing.T) {
e, err := new(V2).Entry(V2Index, 1)
assert.NoError(t, err)
assert.EqualValues(t, 2, e.PackOffset)
}
func TestIndexV2EntryExtendedOffset(t *testing.T) {
e, err := new(V2).Entry(V2Index, 2)
assert.NoError(t, err)
assert.EqualValues(t, 3, e.PackOffset)
}
func TestIndexVersionWidthV2(t *testing.T) {
assert.EqualValues(t, 8, new(V2).Width())
}
func init() {
V2IndexFanout[1] = 1
V2IndexFanout[2] = 2
V2IndexFanout[3] = 3
for i := 3; i < len(V2IndexFanout); i++ {
V2IndexFanout[i] = 3
}
fanout := make([]byte, indexFanoutWidth)
for i, n := range V2IndexFanout {
binary.BigEndian.PutUint32(fanout[i*indexFanoutEntryWidth:], n)
}
buf := make([]byte, 0, indexOffsetV2Start+3*(indexObjectEntryV2Width)+indexObjectLargeOffsetWidth)
buf = append(buf, V2IndexHeader...)
buf = append(buf, fanout...)
buf = append(buf, V2IndexNames...)
buf = append(buf, V2IndexCRCs...)
buf = append(buf, V2IndexOffsets...)
V2Index.r = bytes.NewReader(buf)
}

@ -1,52 +0,0 @@
package pack
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestOffsetReaderAtReadsAtOffset(t *testing.T) {
bo := &OffsetReaderAt{
r: bytes.NewReader([]byte{0x0, 0x1, 0x2, 0x3}),
o: 1,
}
var x1 [1]byte
n1, e1 := bo.Read(x1[:])
assert.NoError(t, e1)
assert.Equal(t, 1, n1)
assert.EqualValues(t, 0x1, x1[0])
var x2 [1]byte
n2, e2 := bo.Read(x2[:])
assert.NoError(t, e2)
assert.Equal(t, 1, n2)
assert.EqualValues(t, 0x2, x2[0])
}
func TestOffsetReaderPropogatesErrors(t *testing.T) {
expected := fmt.Errorf("gitobj/pack: testing")
bo := &OffsetReaderAt{
r: &ErrReaderAt{Err: expected},
o: 1,
}
n, err := bo.Read(make([]byte, 1))
assert.Equal(t, expected, err)
assert.Equal(t, 0, n)
}
type ErrReaderAt struct {
Err error
}
func (e *ErrReaderAt) ReadAt(p []byte, at int64) (n int, err error) {
return 0, e.Err
}

@ -1,46 +0,0 @@
package pack
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestObjectTypeReturnsObjectType(t *testing.T) {
o := &Object{
typ: TypeCommit,
}
assert.Equal(t, TypeCommit, o.Type())
}
func TestObjectUnpackUnpacksData(t *testing.T) {
expected := []byte{0x1, 0x2, 0x3, 0x4}
o := &Object{
data: &ChainSimple{
X: expected,
},
}
data, err := o.Unpack()
assert.Equal(t, expected, data)
assert.NoError(t, err)
}
func TestObjectUnpackPropogatesErrors(t *testing.T) {
expected := fmt.Errorf("gitobj/pack: testing")
o := &Object{
data: &ChainSimple{
Err: expected,
},
}
data, err := o.Unpack()
assert.Nil(t, data)
assert.Equal(t, expected, err)
}

@ -1,41 +0,0 @@
package pack
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDecodePackfileDecodesIntegerVersion(t *testing.T) {
p, err := DecodePackfile(bytes.NewReader([]byte{
'P', 'A', 'C', 'K', // Pack header.
0x0, 0x0, 0x0, 0x2, // Pack version.
0x0, 0x0, 0x0, 0x0, // Number of packed objects.
}))
assert.NoError(t, err)
assert.EqualValues(t, 2, p.Version)
}
func TestDecodePackfileDecodesIntegerCount(t *testing.T) {
p, err := DecodePackfile(bytes.NewReader([]byte{
'P', 'A', 'C', 'K', // Pack header.
0x0, 0x0, 0x0, 0x2, // Pack version.
0x0, 0x0, 0x1, 0x2, // Number of packed objects.
}))
assert.NoError(t, err)
assert.EqualValues(t, 258, p.Objects)
}
func TestDecodePackfileReportsBadHeaders(t *testing.T) {
p, err := DecodePackfile(bytes.NewReader([]byte{
'W', 'R', 'O', 'N', 'G', // Malformed pack header.
0x0, 0x0, 0x0, 0x0, // Pack version.
0x0, 0x0, 0x0, 0x0, // Number of packed objects.
}))
assert.Equal(t, errBadPackHeader, err)
assert.Nil(t, p)
}

@ -1,275 +0,0 @@
package pack
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"sort"
"strings"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPackObjectReturnsObjectWithSingleBaseAtLowOffset(t *testing.T) {
const original = "Hello, world!\n"
compressed, _ := compress(original)
p := &Packfile{
idx: IndexWith(map[string]uint32{
"cccccccccccccccccccccccccccccccccccccccc": 32,
}),
r: bytes.NewReader(append([]byte{
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
// (0001 1000) (msb=0, type=commit, size=14)
0x1e}, compressed...),
),
}
o, err := p.Object(DecodeHex(t, "cccccccccccccccccccccccccccccccccccccccc"))
assert.NoError(t, err)
assert.Equal(t, TypeCommit, o.Type())
unpacked, err := o.Unpack()
assert.Equal(t, []byte(original), unpacked)
assert.NoError(t, err)
}
func TestPackObjectReturnsObjectWithSingleBaseAtHighOffset(t *testing.T) {
original := strings.Repeat("four", 64)
compressed, _ := compress(original)
p := &Packfile{
idx: IndexWith(map[string]uint32{
"cccccccccccccccccccccccccccccccccccccccc": 32,
}),
r: bytes.NewReader(append([]byte{
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
// (1001 0000) (msb=1, type=commit, size=0)
0x90,
// (1000 0000) (msb=0, size=1 -> size=256)
0x10},
compressed...,
)),
}
o, err := p.Object(DecodeHex(t, "cccccccccccccccccccccccccccccccccccccccc"))
assert.NoError(t, err)
assert.Equal(t, TypeCommit, o.Type())
unpacked, err := o.Unpack()
assert.Equal(t, []byte(original), unpacked)
assert.NoError(t, err)
}
func TestPackObjectReturnsObjectWithDeltaBaseOffset(t *testing.T) {
const original = "Hello"
compressed, _ := compress(original)
delta, err := compress(string([]byte{
0x05, // Source size: 5.
0x0e, // Destination size: 14.
0x91, // (1000 0001) (instruction=copy, bitmask=0001)
0x00, // (0000 0000) (offset=0)
0x05, // (0000 0101) (size=5)
0x09, // (0000 0111) (instruction=add, size=7)
// Contents: ...
',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n',
}))
p := &Packfile{
idx: IndexWith(map[string]uint32{
"cccccccccccccccccccccccccccccccccccccccc": uint32(32 + 1 + len(compressed)),
}),
r: bytes.NewReader(append(append([]byte{
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x35, // (0011 0101) (msb=0, type=blob, size=5)
}, compressed...), append([]byte{
0x6e, // (0110 1010) (msb=0, type=obj_ofs_delta, size=10)
0x12, // (0001 0001) (ofs_delta=-17, len(compressed))
}, delta...)...)),
}
o, err := p.Object(DecodeHex(t, "cccccccccccccccccccccccccccccccccccccccc"))
assert.NoError(t, err)
assert.Equal(t, TypeBlob, o.Type())
unpacked, err := o.Unpack()
assert.Equal(t, []byte(original+", world!\n"), unpacked)
assert.NoError(t, err)
}
func TestPackfileObjectReturnsObjectWithDeltaBaseReference(t *testing.T) {
const original = "Hello!\n"
compressed, _ := compress(original)
delta, _ := compress(string([]byte{
0x07, // Source size: 7.
0x0e, // Destination size: 14.
0x91, // (1001 0001) (copy, smask=0001, omask=0001)
0x00, // (0000 0000) (offset=0)
0x05, // (0000 0101) (size=5)
0x7, // (0000 0111) (add, length=6)
',', ' ', 'w', 'o', 'r', 'l', 'd', // (data ...)
0x91, // (1001 0001) (copy, smask=0001, omask=0001)
0x05, // (0000 0101) (offset=5)
0x02, // (0000 0010) (size=2)
}))
p := &Packfile{
idx: IndexWith(map[string]uint32{
"cccccccccccccccccccccccccccccccccccccccc": 32,
"dddddddddddddddddddddddddddddddddddddddd": 52,
}),
r: bytes.NewReader(append(append([]byte{
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x37, // (0011 0101) (msb=0, type=blob, size=7)
}, compressed...), append([]byte{
0x7f, // (0111 1111) (msb=0, type=obj_ref_delta, size=15)
// SHA-1 "cccccccccccccccccccccccccccccccccccccccc",
// original blob contents is "Hello!\n"
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
}, delta...)...)),
}
o, err := p.Object(DecodeHex(t, "dddddddddddddddddddddddddddddddddddddddd"))
assert.NoError(t, err)
assert.Equal(t, TypeBlob, o.Type())
unpacked, err := o.Unpack()
assert.Equal(t, []byte("Hello, world!\n"), unpacked)
assert.NoError(t, err)
}
func TestPackfileClosesReadClosers(t *testing.T) {
r := new(ReaderAtCloser)
p := &Packfile{
r: r,
}
assert.NoError(t, p.Close())
assert.EqualValues(t, 1, r.N)
}
func TestPackfileClosePropogatesCloseErrors(t *testing.T) {
e := fmt.Errorf("gitobj/pack: testing")
p := &Packfile{
r: &ReaderAtCloser{E: e},
}
assert.Equal(t, e, p.Close())
}
type ReaderAtCloser struct {
E error
N uint64
}
func (r *ReaderAtCloser) ReadAt(p []byte, at int64) (int, error) {
return 0, nil
}
func (r *ReaderAtCloser) Close() error {
atomic.AddUint64(&r.N, 1)
return r.E
}
func IndexWith(offsets map[string]uint32) *Index {
header := []byte{
0xff, 0x74, 0x4f, 0x63,
0x00, 0x00, 0x00, 0x02,
}
ns := make([][]byte, 0, len(offsets))
for name, _ := range offsets {
x, _ := hex.DecodeString(name)
ns = append(ns, x)
}
sort.Slice(ns, func(i, j int) bool {
return bytes.Compare(ns[i], ns[j]) < 0
})
fanout := make([]uint32, 256)
for i := 0; i < len(fanout); i++ {
var n uint32
for _, name := range ns {
if name[0] <= byte(i) {
n++
}
}
fanout[i] = n
}
crcs := make([]byte, 4*len(offsets))
for i, _ := range ns {
binary.BigEndian.PutUint32(crcs[i*4:], 0)
}
offs := make([]byte, 4*len(offsets))
for i, name := range ns {
binary.BigEndian.PutUint32(offs[i*4:], offsets[hex.EncodeToString(name)])
}
buf := make([]byte, 0)
buf = append(buf, header...)
for _, f := range fanout {
x := make([]byte, 4)
binary.BigEndian.PutUint32(x, f)
buf = append(buf, x...)
}
for _, n := range ns {
buf = append(buf, n...)
}
buf = append(buf, crcs...)
buf = append(buf, offs...)
return &Index{
fanout: fanout,
r: bytes.NewReader(buf),
version: new(V2),
}
}
func DecodeHex(t *testing.T, str string) []byte {
b, err := hex.DecodeString(str)
if err != nil {
t.Fatalf("gitobj/pack: unexpected hex.DecodeString error: %s", err)
}
return b
}

@ -1,78 +0,0 @@
package pack
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSetOpenOpensAPackedObject(t *testing.T) {
const sha = "decafdecafdecafdecafdecafdecafdecafdecaf"
const data = "Hello, world!\n"
compressed, _ := compress(data)
set := NewSetPacks(&Packfile{
idx: IndexWith(map[string]uint32{
sha: 0,
}),
r: bytes.NewReader(append([]byte{0x3e}, compressed...)),
})
o, err := set.Object(DecodeHex(t, sha))
assert.NoError(t, err)
assert.Equal(t, TypeBlob, o.Type())
unpacked, err := o.Unpack()
assert.NoError(t, err)
assert.Equal(t, []byte(data), unpacked)
}
func TestSetOpenOpensPackedObjectsInPackOrder(t *testing.T) {
p1 := &Packfile{
Objects: 1,
idx: IndexWith(map[string]uint32{
"aa00000000000000000000000000000000000000": 1,
}),
r: bytes.NewReader(nil),
}
p2 := &Packfile{
Objects: 2,
idx: IndexWith(map[string]uint32{
"aa11111111111111111111111111111111111111": 1,
"aa22222222222222222222222222222222222222": 2,
}),
r: bytes.NewReader(nil),
}
p3 := &Packfile{
Objects: 3,
idx: IndexWith(map[string]uint32{
"aa33333333333333333333333333333333333333": 3,
"aa44444444444444444444444444444444444444": 4,
"aa55555555555555555555555555555555555555": 5,
}),
r: bytes.NewReader(nil),
}
set := NewSetPacks(p1, p2, p3)
var visited []*Packfile
set.each(
DecodeHex(t, "aa55555555555555555555555555555555555555"),
func(p *Packfile) (*Object, error) {
visited = append(visited, p)
return nil, errNotFound
},
)
require.Len(t, visited, 3)
assert.EqualValues(t, visited[0].Objects, 3)
assert.EqualValues(t, visited[1].Objects, 2)
assert.EqualValues(t, visited[2].Objects, 1)
}

@ -1,52 +0,0 @@
package pack
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
type PackedObjectStringTestCase struct {
T PackedObjectType
Expected string
Panic bool
}
func (c *PackedObjectStringTestCase) Assert(t *testing.T) {
if c.Panic {
defer func() {
err := recover()
if err == nil {
t.Fatalf("gitobj/pack: expected panic()")
}
assert.Equal(t, c.Expected, fmt.Sprintf("%s", err))
}()
}
assert.Equal(t, c.Expected, c.T.String())
}
func TestPackedObjectTypeString(t *testing.T) {
for desc, c := range map[string]*PackedObjectStringTestCase{
"TypeNone": {T: TypeNone, Expected: "<none>"},
"TypeCommit": {T: TypeCommit, Expected: "commit"},
"TypeTree": {T: TypeTree, Expected: "tree"},
"TypeBlob": {T: TypeBlob, Expected: "blob"},
"TypeTag": {T: TypeTag, Expected: "tag"},
"TypeObjectOffsetDelta": {T: TypeObjectOffsetDelta,
Expected: "obj_ofs_delta"},
"TypeObjectReferenceDelta": {T: TypeObjectReferenceDelta,
Expected: "obj_ref_delta"},
"unknown type": {T: PackedObjectType(5), Panic: true,
Expected: "gitobj/pack: unknown object type: 5"},
} {
t.Run(desc, c.Assert)
}
}

@ -1,65 +0,0 @@
package gitobj
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTagTypeReturnsCorrectObjectType(t *testing.T) {
assert.Equal(t, TagObjectType, new(Tag).Type())
}
func TestTagEncode(t *testing.T) {
tag := &Tag{
Object: []byte("aaaaaaaaaaaaaaaaaaaa"),
ObjectType: CommitObjectType,
Name: "v2.4.0",
Tagger: "A U Thor <author@example.com>",
Message: "The quick brown fox jumps over the lazy dog.",
}
buf := new(bytes.Buffer)
n, err := tag.Encode(buf)
assert.Nil(t, err)
assert.EqualValues(t, buf.Len(), n)
assertLine(t, buf, "object 6161616161616161616161616161616161616161")
assertLine(t, buf, "type commit")
assertLine(t, buf, "tag v2.4.0")
assertLine(t, buf, "tagger A U Thor <author@example.com>")
assertLine(t, buf, "")
assertLine(t, buf, "The quick brown fox jumps over the lazy dog.")
assert.Equal(t, 0, buf.Len())
}
func TestTagDecode(t *testing.T) {
from := new(bytes.Buffer)
fmt.Fprintf(from, "object 6161616161616161616161616161616161616161\n")
fmt.Fprintf(from, "type commit\n")
fmt.Fprintf(from, "tag v2.4.0\n")
fmt.Fprintf(from, "tagger A U Thor <author@example.com>\n")
fmt.Fprintf(from, "\n")
fmt.Fprintf(from, "The quick brown fox jumps over the lazy dog.\n")
flen := from.Len()
tag := new(Tag)
n, err := tag.Decode(from, int64(flen))
assert.Nil(t, err)
assert.Equal(t, n, flen)
assert.Equal(t, []byte("aaaaaaaaaaaaaaaaaaaa"), tag.Object)
assert.Equal(t, CommitObjectType, tag.ObjectType)
assert.Equal(t, "v2.4.0", tag.Name)
assert.Equal(t, "A U Thor <author@example.com>", tag.Tagger)
assert.Equal(t, "The quick brown fox jumps over the lazy dog.", tag.Message)
}

@ -1,344 +0,0 @@
package gitobj
import (
"bufio"
"bytes"
"fmt"
"sort"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTreeReturnsCorrectObjectType(t *testing.T) {
assert.Equal(t, TreeObjectType, new(Tree).Type())
}
func TestTreeEncoding(t *testing.T) {
tree := &Tree{
Entries: []*TreeEntry{
{
Name: "a.dat",
Oid: []byte("aaaaaaaaaaaaaaaaaaaa"),
Filemode: 0100644,
},
{
Name: "subdir",
Oid: []byte("bbbbbbbbbbbbbbbbbbbb"),
Filemode: 040000,
},
{
Name: "submodule",
Oid: []byte("cccccccccccccccccccc"),
Filemode: 0160000,
},
},
}
buf := new(bytes.Buffer)
n, err := tree.Encode(buf)
assert.Nil(t, err)
assert.NotEqual(t, 0, n)
assertTreeEntry(t, buf, "a.dat", []byte("aaaaaaaaaaaaaaaaaaaa"), 0100644)
assertTreeEntry(t, buf, "subdir", []byte("bbbbbbbbbbbbbbbbbbbb"), 040000)
assertTreeEntry(t, buf, "submodule", []byte("cccccccccccccccccccc"), 0160000)
assert.Equal(t, 0, buf.Len())
}
func TestTreeDecoding(t *testing.T) {
from := new(bytes.Buffer)
fmt.Fprintf(from, "%s %s\x00%s",
strconv.FormatInt(int64(0100644), 8),
"a.dat", []byte("aaaaaaaaaaaaaaaaaaaa"))
fmt.Fprintf(from, "%s %s\x00%s",
strconv.FormatInt(int64(040000), 8),
"subdir", []byte("bbbbbbbbbbbbbbbbbbbb"))
fmt.Fprintf(from, "%s %s\x00%s",
strconv.FormatInt(int64(0120000), 8),
"symlink", []byte("cccccccccccccccccccc"))
fmt.Fprintf(from, "%s %s\x00%s",
strconv.FormatInt(int64(0160000), 8),
"submodule", []byte("dddddddddddddddddddd"))
flen := from.Len()
tree := new(Tree)
n, err := tree.Decode(from, int64(flen))
assert.Nil(t, err)
assert.Equal(t, flen, n)
require.Equal(t, 4, len(tree.Entries))
assert.Equal(t, &TreeEntry{
Name: "a.dat",
Oid: []byte("aaaaaaaaaaaaaaaaaaaa"),
Filemode: 0100644,
}, tree.Entries[0])
assert.Equal(t, &TreeEntry{
Name: "subdir",
Oid: []byte("bbbbbbbbbbbbbbbbbbbb"),
Filemode: 040000,
}, tree.Entries[1])
assert.Equal(t, &TreeEntry{
Name: "symlink",
Oid: []byte("cccccccccccccccccccc"),
Filemode: 0120000,
}, tree.Entries[2])
assert.Equal(t, &TreeEntry{
Name: "submodule",
Oid: []byte("dddddddddddddddddddd"),
Filemode: 0160000,
}, tree.Entries[3])
}
func TestTreeDecodingShaBoundary(t *testing.T) {
var from bytes.Buffer
fmt.Fprintf(&from, "%s %s\x00%s",
strconv.FormatInt(int64(0100644), 8),
"a.dat", []byte("aaaaaaaaaaaaaaaaaaaa"))
flen := from.Len()
tree := new(Tree)
n, err := tree.Decode(bufio.NewReaderSize(&from, flen-2), int64(flen))
assert.Nil(t, err)
assert.Equal(t, flen, n)
require.Len(t, tree.Entries, 1)
assert.Equal(t, &TreeEntry{
Name: "a.dat",
Oid: []byte("aaaaaaaaaaaaaaaaaaaa"),
Filemode: 0100644,
}, tree.Entries[0])
}
func TestTreeMergeReplaceElements(t *testing.T) {
e1 := &TreeEntry{Name: "a", Filemode: 0100644, Oid: []byte{0x1}}
e2 := &TreeEntry{Name: "b", Filemode: 0100644, Oid: []byte{0x2}}
e3 := &TreeEntry{Name: "c", Filemode: 0100644, Oid: []byte{0x3}}
e4 := &TreeEntry{Name: "b", Filemode: 0100644, Oid: []byte{0x4}}
e5 := &TreeEntry{Name: "c", Filemode: 0100644, Oid: []byte{0x5}}
t1 := &Tree{Entries: []*TreeEntry{e1, e2, e3}}
t2 := t1.Merge(e4, e5)
require.Len(t, t1.Entries, 3)
assert.True(t, bytes.Equal(t1.Entries[0].Oid, []byte{0x1}))
assert.True(t, bytes.Equal(t1.Entries[1].Oid, []byte{0x2}))
assert.True(t, bytes.Equal(t1.Entries[2].Oid, []byte{0x3}))
require.Len(t, t2.Entries, 3)
assert.True(t, bytes.Equal(t2.Entries[0].Oid, []byte{0x1}))
assert.True(t, bytes.Equal(t2.Entries[1].Oid, []byte{0x4}))
assert.True(t, bytes.Equal(t2.Entries[2].Oid, []byte{0x5}))
}
func TestMergeInsertElementsInSubtreeOrder(t *testing.T) {
e1 := &TreeEntry{Name: "a-b", Filemode: 0100644, Oid: []byte{0x1}}
e2 := &TreeEntry{Name: "a", Filemode: 040000, Oid: []byte{0x2}}
e3 := &TreeEntry{Name: "a=", Filemode: 0100644, Oid: []byte{0x3}}
e4 := &TreeEntry{Name: "a-", Filemode: 0100644, Oid: []byte{0x4}}
t1 := &Tree{Entries: []*TreeEntry{e1, e2, e3}}
t2 := t1.Merge(e4)
require.Len(t, t1.Entries, 3)
assert.True(t, bytes.Equal(t1.Entries[0].Oid, []byte{0x1}))
assert.True(t, bytes.Equal(t1.Entries[1].Oid, []byte{0x2}))
assert.True(t, bytes.Equal(t1.Entries[2].Oid, []byte{0x3}))
assert.True(t, bytes.Equal(t2.Entries[0].Oid, []byte{0x4}))
assert.True(t, bytes.Equal(t2.Entries[1].Oid, []byte{0x1}))
assert.True(t, bytes.Equal(t2.Entries[2].Oid, []byte{0x2}))
assert.True(t, bytes.Equal(t2.Entries[3].Oid, []byte{0x3}))
}
type TreeEntryTypeTestCase struct {
Filemode int32
Expected ObjectType
}
func (c *TreeEntryTypeTestCase) Assert(t *testing.T) {
e := &TreeEntry{Filemode: c.Filemode}
got := e.Type()
assert.Equal(t, c.Expected, got,
"gitobj: expected type: %s, got: %s", c.Expected, got)
}
func TestTreeEntryTypeResolution(t *testing.T) {
for desc, c := range map[string]*TreeEntryTypeTestCase{
"blob": {0100644, BlobObjectType},
"subtree": {040000, TreeObjectType},
"symlink": {0120000, BlobObjectType},
"commit": {0160000, CommitObjectType},
} {
t.Run(desc, c.Assert)
}
}
func TestTreeEntryTypeResolutionUnknown(t *testing.T) {
e := &TreeEntry{Filemode: -1}
defer func() {
if err := recover(); err == nil {
t.Fatal("gitobj: expected panic(), got none")
} else {
assert.Equal(t, "gitobj: unknown object type: -1", err)
}
}()
e.Type()
}
func TestSubtreeOrder(t *testing.T) {
// The below list (e1, e2, ..., e5) is entered in subtree order: that
// is, lexicographically byte-ordered as if blobs end in a '\0', and
// sub-trees end in a '/'.
//
// See:
// http://public-inbox.org/git/7vac6jfzem.fsf@assigned-by-dhcp.cox.net
e1 := &TreeEntry{Filemode: 0100644, Name: "a-"}
e2 := &TreeEntry{Filemode: 0100644, Name: "a-b"}
e3 := &TreeEntry{Filemode: 040000, Name: "a"}
e4 := &TreeEntry{Filemode: 0100644, Name: "a="}
e5 := &TreeEntry{Filemode: 0100644, Name: "a=b"}
// Create a set of entries in the wrong order:
entries := []*TreeEntry{e3, e4, e1, e5, e2}
sort.Sort(SubtreeOrder(entries))
// Assert that they are in the correct order after sorting in sub-tree
// order:
require.Len(t, entries, 5)
assert.Equal(t, "a-", entries[0].Name)
assert.Equal(t, "a-b", entries[1].Name)
assert.Equal(t, "a", entries[2].Name)
assert.Equal(t, "a=", entries[3].Name)
assert.Equal(t, "a=b", entries[4].Name)
}
func TestSubtreeOrderReturnsEmptyForOutOfBounds(t *testing.T) {
o := SubtreeOrder([]*TreeEntry{{Name: "a"}})
assert.Equal(t, "", o.Name(len(o)+1))
}
func TestSubtreeOrderReturnsEmptyForNilElements(t *testing.T) {
o := SubtreeOrder([]*TreeEntry{nil})
assert.Equal(t, "", o.Name(0))
}
func TestTreeEqualReturnsTrueWithUnchangedContents(t *testing.T) {
t1 := &Tree{Entries: []*TreeEntry{
{Name: "a.dat", Filemode: 0100644, Oid: make([]byte, 20)},
}}
t2 := &Tree{Entries: []*TreeEntry{
{Name: "a.dat", Filemode: 0100644, Oid: make([]byte, 20)},
}}
assert.True(t, t1.Equal(t2))
}
func TestTreeEqualReturnsFalseWithChangedContents(t *testing.T) {
t1 := &Tree{Entries: []*TreeEntry{
{Name: "a.dat", Filemode: 0100644, Oid: make([]byte, 20)},
{Name: "b.dat", Filemode: 0100644, Oid: make([]byte, 20)},
}}
t2 := &Tree{Entries: []*TreeEntry{
{Name: "a.dat", Filemode: 0100644, Oid: make([]byte, 20)},
{Name: "c.dat", Filemode: 0100644, Oid: make([]byte, 20)},
}}
assert.False(t, t1.Equal(t2))
}
func TestTreeEqualReturnsTrueWhenOneTreeIsNil(t *testing.T) {
t1 := &Tree{Entries: []*TreeEntry{
{Name: "a.dat", Filemode: 0100644, Oid: make([]byte, 20)},
}}
t2 := (*Tree)(nil)
assert.False(t, t1.Equal(t2))
assert.False(t, t2.Equal(t1))
}
func TestTreeEqualReturnsTrueWhenBothTreesAreNil(t *testing.T) {
t1 := (*Tree)(nil)
t2 := (*Tree)(nil)
assert.True(t, t1.Equal(t2))
}
func TestTreeEntryEqualReturnsTrueWhenEntriesAreTheSame(t *testing.T) {
e1 := &TreeEntry{Name: "a.dat", Filemode: 0100644, Oid: make([]byte, 20)}
e2 := &TreeEntry{Name: "a.dat", Filemode: 0100644, Oid: make([]byte, 20)}
assert.True(t, e1.Equal(e2))
}
func TestTreeEntryEqualReturnsFalseWhenDifferentNames(t *testing.T) {
e1 := &TreeEntry{Name: "a.dat", Filemode: 0100644, Oid: make([]byte, 20)}
e2 := &TreeEntry{Name: "b.dat", Filemode: 0100644, Oid: make([]byte, 20)}
assert.False(t, e1.Equal(e2))
}
func TestTreeEntryEqualReturnsFalseWhenDifferentOids(t *testing.T) {
e1 := &TreeEntry{Name: "a.dat", Filemode: 0100644, Oid: make([]byte, 20)}
e2 := &TreeEntry{Name: "a.dat", Filemode: 0100644, Oid: make([]byte, 20)}
e2.Oid[0] = 1
assert.False(t, e1.Equal(e2))
}
func TestTreeEntryEqualReturnsFalseWhenDifferentFilemodes(t *testing.T) {
e1 := &TreeEntry{Name: "a.dat", Filemode: 0100644, Oid: make([]byte, 20)}
e2 := &TreeEntry{Name: "a.dat", Filemode: 0100755, Oid: make([]byte, 20)}
assert.False(t, e1.Equal(e2))
}
func TestTreeEntryEqualReturnsFalseWhenOneEntryIsNil(t *testing.T) {
e1 := &TreeEntry{Name: "a.dat", Filemode: 0100644, Oid: make([]byte, 20)}
e2 := (*TreeEntry)(nil)
assert.False(t, e1.Equal(e2))
}
func TestTreeEntryEqualReturnsTrueWhenBothEntriesAreNil(t *testing.T) {
e1 := (*TreeEntry)(nil)
e2 := (*TreeEntry)(nil)
assert.True(t, e1.Equal(e2))
}
func assertTreeEntry(t *testing.T, buf *bytes.Buffer,
name string, oid []byte, mode int32) {
fmode, err := buf.ReadBytes(' ')
assert.Nil(t, err)
assert.Equal(t, []byte(strconv.FormatInt(int64(mode), 8)+" "), fmode)
fname, err := buf.ReadBytes('\x00')
assert.Nil(t, err)
assert.Equal(t, []byte(name+"\x00"), fname)
var sha [20]byte
_, err = buf.Read(sha[:])
assert.Nil(t, err)
assert.Equal(t, oid, sha[:])
}

@ -1,22 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

@ -1,11 +0,0 @@
language: go
go: 1.2
install:
- go get -v code.google.com/p/go.tools/cmd/cover
script:
- go test -v -tags=disableunsafe ./spew
- go test -v -tags=testcgo ./spew -covermode=count -coverprofile=profile.cov
after_success:
- go get -v github.com/mattn/goveralls
- export PATH=$PATH:$HOME/gopath/bin
- goveralls -coverprofile=profile.cov -service=travis-ci

@ -1,13 +0,0 @@
Copyright (c) 2012-2013 Dave Collins <dave@davec.name>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

@ -1,194 +0,0 @@
go-spew
=======
[![Build Status](https://travis-ci.org/davecgh/go-spew.png?branch=master)]
(https://travis-ci.org/davecgh/go-spew) [![Coverage Status]
(https://coveralls.io/repos/davecgh/go-spew/badge.png?branch=master)]
(https://coveralls.io/r/davecgh/go-spew?branch=master)
Go-spew implements a deep pretty printer for Go data structures to aid in
debugging. A comprehensive suite of tests with 100% test coverage is provided
to ensure proper functionality. See `test_coverage.txt` for the gocov coverage
report. Go-spew is licensed under the liberal ISC license, so it may be used in
open source or commercial projects.
If you're interested in reading about how this package came to life and some
of the challenges involved in providing a deep pretty printer, there is a blog
post about it
[here](https://blog.cyphertite.com/go-spew-a-journey-into-dumping-go-data-structures/).
## Documentation
[![GoDoc](https://godoc.org/github.com/davecgh/go-spew/spew?status.png)]
(http://godoc.org/github.com/davecgh/go-spew/spew)
Full `go doc` style documentation for the project can be viewed online without
installing this package by using the excellent GoDoc site here:
http://godoc.org/github.com/davecgh/go-spew/spew
You can also view the documentation locally once the package is installed with
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
http://localhost:6060/pkg/github.com/davecgh/go-spew/spew
## Installation
```bash
$ go get -u github.com/davecgh/go-spew/spew
```
## Quick Start
Add this import line to the file you're working in:
```Go
import "github.com/davecgh/go-spew/spew"
```
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
```Go
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
```
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with %v (most
compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types
and pointer addresses):
```Go
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
```
## Debugging a Web Application Example
Here is an example of how you can use `spew.Sdump()` to help debug a web application. Please be sure to wrap your output using the `html.EscapeString()` function for safety reasons. You should also only use this debugging technique in a development environment, never in production.
```Go
package main
import (
"fmt"
"html"
"net/http"
"github.com/davecgh/go-spew/spew"
)
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
fmt.Fprintf(w, "Hi there, %s!", r.URL.Path[1:])
fmt.Fprintf(w, "<!--\n" + html.EscapeString(spew.Sdump(w)) + "\n-->")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
```
## Sample Dump Output
```
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) {
(string) "one": (bool) true
}
}
([]uint8) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
```
## Sample Formatter Output
Double pointer to a uint8:
```
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
```
Pointer to circular struct with a uint8 field and a pointer to itself:
```
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
```
## Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available via the
spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
```
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables. This option
relies on access to the unsafe package, so it will not have any effect when
running in environments without access to the unsafe package such as Google
App Engine or with the "disableunsafe" build tag specified.
Pointer method invocation is enabled by default.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are supported,
with other types sorted according to the reflect.Value.String() output
which guarantees display stability. Natural map order is used by
default.
* SpewKeys
SpewKeys specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only considered
if SortKeys is true.
```
## Unsafe Package Dependency
This package relies on the unsafe package to perform some of the more advanced
features, however it also supports a "limited" mode which allows it to work in
environments where the unsafe package is not available. By default, it will
operate in this mode on Google App Engine. The "disableunsafe" build tag may
also be specified to force the package to build without using the unsafe
package.
## License
Go-spew is licensed under the liberal ISC License.

@ -1,22 +0,0 @@
#!/bin/sh
# This script uses gocov to generate a test coverage report.
# The gocov tool my be obtained with the following command:
# go get github.com/axw/gocov/gocov
#
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
# Check for gocov.
if ! type gocov >/dev/null 2>&1; then
echo >&2 "This script requires the gocov tool."
echo >&2 "You may obtain it with the following command:"
echo >&2 "go get github.com/axw/gocov/gocov"
exit 1
fi
# Only run the cgo tests if gcc is installed.
if type gcc >/dev/null 2>&1; then
(cd spew && gocov test -tags testcgo | gocov report)
else
(cd spew && gocov test | gocov report)
fi

@ -1,151 +0,0 @@
// Copyright (c) 2015 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine and "-tags disableunsafe"
// is not added to the go build command line.
// +build !appengine,!disableunsafe
package spew
import (
"reflect"
"unsafe"
)
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = false
// ptrSize is the size of a pointer on the current arch.
ptrSize = unsafe.Sizeof((*byte)(nil))
)
var (
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
// internal reflect.Value fields. These values are valid before golang
// commit ecccf07e7f9d which changed the format. The are also valid
// after commit 82f48826c6c7 which changed the format again to mirror
// the original format. Code in the init function updates these offsets
// as necessary.
offsetPtr = uintptr(ptrSize)
offsetScalar = uintptr(0)
offsetFlag = uintptr(ptrSize * 2)
// flagKindWidth and flagKindShift indicate various bits that the
// reflect package uses internally to track kind information.
//
// flagRO indicates whether or not the value field of a reflect.Value is
// read-only.
//
// flagIndir indicates whether the value field of a reflect.Value is
// the actual data or a pointer to the data.
//
// These values are valid before golang commit 90a7c3c86944 which
// changed their positions. Code in the init function updates these
// flags as necessary.
flagKindWidth = uintptr(5)
flagKindShift = uintptr(flagKindWidth - 1)
flagRO = uintptr(1 << 0)
flagIndir = uintptr(1 << 1)
)
func init() {
// Older versions of reflect.Value stored small integers directly in the
// ptr field (which is named val in the older versions). Versions
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
// scalar for this purpose which unfortunately came before the flag
// field, so the offset of the flag field is different for those
// versions.
//
// This code constructs a new reflect.Value from a known small integer
// and checks if the size of the reflect.Value struct indicates it has
// the scalar field. When it does, the offsets are updated accordingly.
vv := reflect.ValueOf(0xf00)
if unsafe.Sizeof(vv) == (ptrSize * 4) {
offsetScalar = ptrSize * 2
offsetFlag = ptrSize * 3
}
// Commit 90a7c3c86944 changed the flag positions such that the low
// order bits are the kind. This code extracts the kind from the flags
// field and ensures it's the correct type. When it's not, the flag
// order has been changed to the newer format, so the flags are updated
// accordingly.
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
upfv := *(*uintptr)(upf)
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
flagKindShift = 0
flagRO = 1 << 5
flagIndir = 1 << 6
// Commit adf9b30e5594 modified the flags to separate the
// flagRO flag into two bits which specifies whether or not the
// field is embedded. This causes flagIndir to move over a bit
// and means that flagRO is the combination of either of the
// original flagRO bit and the new bit.
//
// This code detects the change by extracting what used to be
// the indirect bit to ensure it's set. When it's not, the flag
// order has been changed to the newer format, so the flags are
// updated accordingly.
if upfv&flagIndir == 0 {
flagRO = 3 << 5
flagIndir = 1 << 7
}
}
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
// the typical safety restrictions preventing access to unaddressable and
// unexported data. It works by digging the raw pointer to the underlying
// value out of the protected value and generating a new unprotected (unsafe)
// reflect.Value to it.
//
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
indirects := 1
vt := v.Type()
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
if rvf&flagIndir != 0 {
vt = reflect.PtrTo(v.Type())
indirects++
} else if offsetScalar != 0 {
// The value is in the scalar field when it's not one of the
// reference types.
switch vt.Kind() {
case reflect.Uintptr:
case reflect.Chan:
case reflect.Func:
case reflect.Map:
case reflect.Ptr:
case reflect.UnsafePointer:
default:
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
offsetScalar)
}
}
pv := reflect.NewAt(vt, upv)
rv = pv
for i := 0; i < indirects; i++ {
rv = rv.Elem()
}
return rv
}

@ -1,37 +0,0 @@
// Copyright (c) 2015 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when either the code is running on Google App Engine or "-tags disableunsafe"
// is added to the go build command line.
// +build appengine disableunsafe
package spew
import "reflect"
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = true
)
// unsafeReflectValue typically converts the passed reflect.Value into a one
// that bypasses the typical safety restrictions preventing access to
// unaddressable and unexported data. However, doing this relies on access to
// the unsafe package. This is a stub version which simply returns the passed
// reflect.Value when the unsafe package is not available.
func unsafeReflectValue(v reflect.Value) reflect.Value {
return v
}

@ -1,341 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"reflect"
"sort"
"strconv"
)
// Some constants in the form of bytes to avoid string overhead. This mirrors
// the technique used in the fmt package.
var (
panicBytes = []byte("(PANIC=")
plusBytes = []byte("+")
iBytes = []byte("i")
trueBytes = []byte("true")
falseBytes = []byte("false")
interfaceBytes = []byte("(interface {})")
commaNewlineBytes = []byte(",\n")
newlineBytes = []byte("\n")
openBraceBytes = []byte("{")
openBraceNewlineBytes = []byte("{\n")
closeBraceBytes = []byte("}")
asteriskBytes = []byte("*")
colonBytes = []byte(":")
colonSpaceBytes = []byte(": ")
openParenBytes = []byte("(")
closeParenBytes = []byte(")")
spaceBytes = []byte(" ")
pointerChainBytes = []byte("->")
nilAngleBytes = []byte("<nil>")
maxNewlineBytes = []byte("<max depth reached>\n")
maxShortBytes = []byte("<max>")
circularBytes = []byte("<already shown>")
circularShortBytes = []byte("<shown>")
invalidAngleBytes = []byte("<invalid>")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
percentBytes = []byte("%")
precisionBytes = []byte(".")
openAngleBytes = []byte("<")
closeAngleBytes = []byte(">")
openMapBytes = []byte("map[")
closeMapBytes = []byte("]")
lenEqualsBytes = []byte("len=")
capEqualsBytes = []byte("cap=")
)
// hexDigits is used to map a decimal value to a hex digit.
var hexDigits = "0123456789abcdef"
// catchPanic handles any panics that might occur during the handleMethods
// calls.
func catchPanic(w io.Writer, v reflect.Value) {
if err := recover(); err != nil {
w.Write(panicBytes)
fmt.Fprintf(w, "%v", err)
w.Write(closeParenBytes)
}
}
// handleMethods attempts to call the Error and String methods on the underlying
// type the passed reflect.Value represents and outputes the result to Writer w.
//
// It handles panics in any called methods by catching and displaying the error
// as the formatted value.
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
// We need an interface to check if the type implements the error or
// Stringer interface. However, the reflect package won't give us an
// interface on certain things like unexported struct fields in order
// to enforce visibility rules. We use unsafe, when it's available,
// to bypass these restrictions since this package does not mutate the
// values.
if !v.CanInterface() {
if UnsafeDisabled {
return false
}
v = unsafeReflectValue(v)
}
// Choose whether or not to do error and Stringer interface lookups against
// the base type or a pointer to the base type depending on settings.
// Technically calling one of these methods with a pointer receiver can
// mutate the value, however, types which choose to satisify an error or
// Stringer interface with a pointer receiver should not be mutating their
// state inside these interface methods.
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
v = unsafeReflectValue(v)
}
if v.CanAddr() {
v = v.Addr()
}
// Is it an error or Stringer?
switch iface := v.Interface().(type) {
case error:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.Error()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.Error()))
return true
case fmt.Stringer:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.String()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.String()))
return true
}
return false
}
// printBool outputs a boolean value as true or false to Writer w.
func printBool(w io.Writer, val bool) {
if val {
w.Write(trueBytes)
} else {
w.Write(falseBytes)
}
}
// printInt outputs a signed integer value to Writer w.
func printInt(w io.Writer, val int64, base int) {
w.Write([]byte(strconv.FormatInt(val, base)))
}
// printUint outputs an unsigned integer value to Writer w.
func printUint(w io.Writer, val uint64, base int) {
w.Write([]byte(strconv.FormatUint(val, base)))
}
// printFloat outputs a floating point value using the specified precision,
// which is expected to be 32 or 64bit, to Writer w.
func printFloat(w io.Writer, val float64, precision int) {
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
}
// printComplex outputs a complex value using the specified float precision
// for the real and imaginary parts to Writer w.
func printComplex(w io.Writer, c complex128, floatPrecision int) {
r := real(c)
w.Write(openParenBytes)
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
i := imag(c)
if i >= 0 {
w.Write(plusBytes)
}
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
w.Write(iBytes)
w.Write(closeParenBytes)
}
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
// prefix to Writer w.
func printHexPtr(w io.Writer, p uintptr) {
// Null pointer.
num := uint64(p)
if num == 0 {
w.Write(nilAngleBytes)
return
}
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
buf := make([]byte, 18)
// It's simpler to construct the hex string right to left.
base := uint64(16)
i := len(buf) - 1
for num >= base {
buf[i] = hexDigits[num%base]
num /= base
i--
}
buf[i] = hexDigits[num]
// Add '0x' prefix.
i--
buf[i] = 'x'
i--
buf[i] = '0'
// Strip unused leading bytes.
buf = buf[i:]
w.Write(buf)
}
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
// elements to be sorted.
type valuesSorter struct {
values []reflect.Value
strings []string // either nil or same len and values
cs *ConfigState
}
// newValuesSorter initializes a valuesSorter instance, which holds a set of
// surrogate keys on which the data should be sorted. It uses flags in
// ConfigState to decide if and how to populate those surrogate keys.
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
vs := &valuesSorter{values: values, cs: cs}
if canSortSimply(vs.values[0].Kind()) {
return vs
}
if !cs.DisableMethods {
vs.strings = make([]string, len(values))
for i := range vs.values {
b := bytes.Buffer{}
if !handleMethods(cs, &b, vs.values[i]) {
vs.strings = nil
break
}
vs.strings[i] = b.String()
}
}
if vs.strings == nil && cs.SpewKeys {
vs.strings = make([]string, len(values))
for i := range vs.values {
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
}
}
return vs
}
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
// directly, or whether it should be considered for sorting by surrogate keys
// (if the ConfigState allows it).
func canSortSimply(kind reflect.Kind) bool {
// This switch parallels valueSortLess, except for the default case.
switch kind {
case reflect.Bool:
return true
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return true
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return true
case reflect.Float32, reflect.Float64:
return true
case reflect.String:
return true
case reflect.Uintptr:
return true
case reflect.Array:
return true
}
return false
}
// Len returns the number of values in the slice. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Len() int {
return len(s.values)
}
// Swap swaps the values at the passed indices. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Swap(i, j int) {
s.values[i], s.values[j] = s.values[j], s.values[i]
if s.strings != nil {
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
}
}
// valueSortLess returns whether the first value should sort before the second
// value. It is used by valueSorter.Less as part of the sort.Interface
// implementation.
func valueSortLess(a, b reflect.Value) bool {
switch a.Kind() {
case reflect.Bool:
return !a.Bool() && b.Bool()
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return a.Int() < b.Int()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return a.Uint() < b.Uint()
case reflect.Float32, reflect.Float64:
return a.Float() < b.Float()
case reflect.String:
return a.String() < b.String()
case reflect.Uintptr:
return a.Uint() < b.Uint()
case reflect.Array:
// Compare the contents of both arrays.
l := a.Len()
for i := 0; i < l; i++ {
av := a.Index(i)
bv := b.Index(i)
if av.Interface() == bv.Interface() {
continue
}
return valueSortLess(av, bv)
}
}
return a.String() < b.String()
}
// Less returns whether the value at index i should sort before the
// value at index j. It is part of the sort.Interface implementation.
func (s *valuesSorter) Less(i, j int) bool {
if s.strings == nil {
return valueSortLess(s.values[i], s.values[j])
}
return s.strings[i] < s.strings[j]
}
// sortValues is a sort function that handles both native types and any type that
// can be converted to error or Stringer. Other inputs are sorted according to
// their Value.String() value to ensure display stability.
func sortValues(values []reflect.Value, cs *ConfigState) {
if len(values) == 0 {
return
}
sort.Sort(newValuesSorter(values, cs))
}

@ -1,298 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew_test
import (
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
)
// custom type to test Stinger interface on non-pointer receiver.
type stringer string
// String implements the Stringer interface for testing invocation of custom
// stringers on types with non-pointer receivers.
func (s stringer) String() string {
return "stringer " + string(s)
}
// custom type to test Stinger interface on pointer receiver.
type pstringer string
// String implements the Stringer interface for testing invocation of custom
// stringers on types with only pointer receivers.
func (s *pstringer) String() string {
return "stringer " + string(*s)
}
// xref1 and xref2 are cross referencing structs for testing circular reference
// detection.
type xref1 struct {
ps2 *xref2
}
type xref2 struct {
ps1 *xref1
}
// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
// reference for testing detection.
type indirCir1 struct {
ps2 *indirCir2
}
type indirCir2 struct {
ps3 *indirCir3
}
type indirCir3 struct {
ps1 *indirCir1
}
// embed is used to test embedded structures.
type embed struct {
a string
}
// embedwrap is used to test embedded structures.
type embedwrap struct {
*embed
e *embed
}
// panicer is used to intentionally cause a panic for testing spew properly
// handles them
type panicer int
func (p panicer) String() string {
panic("test panic")
}
// customError is used to test custom error interface invocation.
type customError int
func (e customError) Error() string {
return fmt.Sprintf("error: %d", int(e))
}
// stringizeWants converts a slice of wanted test output into a format suitable
// for a test error message.
func stringizeWants(wants []string) string {
s := ""
for i, want := range wants {
if i > 0 {
s += fmt.Sprintf("want%d: %s", i+1, want)
} else {
s += "want: " + want
}
}
return s
}
// testFailed returns whether or not a test failed by checking if the result
// of the test is in the slice of wanted strings.
func testFailed(result string, wants []string) bool {
for _, want := range wants {
if result == want {
return false
}
}
return true
}
type sortableStruct struct {
x int
}
func (ss sortableStruct) String() string {
return fmt.Sprintf("ss.%d", ss.x)
}
type unsortableStruct struct {
x int
}
type sortTestCase struct {
input []reflect.Value
expected []reflect.Value
}
func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) {
getInterfaces := func(values []reflect.Value) []interface{} {
interfaces := []interface{}{}
for _, v := range values {
interfaces = append(interfaces, v.Interface())
}
return interfaces
}
for _, test := range tests {
spew.SortValues(test.input, cs)
// reflect.DeepEqual cannot really make sense of reflect.Value,
// probably because of all the pointer tricks. For instance,
// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
// instead.
input := getInterfaces(test.input)
expected := getInterfaces(test.expected)
if !reflect.DeepEqual(input, expected) {
t.Errorf("Sort mismatch:\n %v != %v", input, expected)
}
}
}
// TestSortValues ensures the sort functionality for relect.Value based sorting
// works as intended.
func TestSortValues(t *testing.T) {
v := reflect.ValueOf
a := v("a")
b := v("b")
c := v("c")
embedA := v(embed{"a"})
embedB := v(embed{"b"})
embedC := v(embed{"c"})
tests := []sortTestCase{
// No values.
{
[]reflect.Value{},
[]reflect.Value{},
},
// Bools.
{
[]reflect.Value{v(false), v(true), v(false)},
[]reflect.Value{v(false), v(false), v(true)},
},
// Ints.
{
[]reflect.Value{v(2), v(1), v(3)},
[]reflect.Value{v(1), v(2), v(3)},
},
// Uints.
{
[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))},
[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))},
},
// Floats.
{
[]reflect.Value{v(2.0), v(1.0), v(3.0)},
[]reflect.Value{v(1.0), v(2.0), v(3.0)},
},
// Strings.
{
[]reflect.Value{b, a, c},
[]reflect.Value{a, b, c},
},
// Array
{
[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})},
[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})},
},
// Uintptrs.
{
[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))},
[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))},
},
// SortableStructs.
{
// Note: not sorted - DisableMethods is set.
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
},
// UnsortableStructs.
{
// Note: not sorted - SpewKeys is false.
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
},
// Invalid.
{
[]reflect.Value{embedB, embedA, embedC},
[]reflect.Value{embedB, embedA, embedC},
},
}
cs := spew.ConfigState{DisableMethods: true, SpewKeys: false}
helpTestSortValues(tests, &cs, t)
}
// TestSortValuesWithMethods ensures the sort functionality for relect.Value
// based sorting works as intended when using string methods.
func TestSortValuesWithMethods(t *testing.T) {
v := reflect.ValueOf
a := v("a")
b := v("b")
c := v("c")
tests := []sortTestCase{
// Ints.
{
[]reflect.Value{v(2), v(1), v(3)},
[]reflect.Value{v(1), v(2), v(3)},
},
// Strings.
{
[]reflect.Value{b, a, c},
[]reflect.Value{a, b, c},
},
// SortableStructs.
{
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
},
// UnsortableStructs.
{
// Note: not sorted - SpewKeys is false.
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
},
}
cs := spew.ConfigState{DisableMethods: false, SpewKeys: false}
helpTestSortValues(tests, &cs, t)
}
// TestSortValuesWithSpew ensures the sort functionality for relect.Value
// based sorting works as intended when using spew to stringify keys.
func TestSortValuesWithSpew(t *testing.T) {
v := reflect.ValueOf
a := v("a")
b := v("b")
c := v("c")
tests := []sortTestCase{
// Ints.
{
[]reflect.Value{v(2), v(1), v(3)},
[]reflect.Value{v(1), v(2), v(3)},
},
// Strings.
{
[]reflect.Value{b, a, c},
[]reflect.Value{a, b, c},
},
// SortableStructs.
{
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
},
// UnsortableStructs.
{
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})},
},
}
cs := spew.ConfigState{DisableMethods: true, SpewKeys: true}
helpTestSortValues(tests, &cs, t)
}

@ -1,297 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"os"
)
// ConfigState houses the configuration options used by spew to format and
// display values. There is a global instance, Config, that is used to control
// all top-level Formatter and Dump functionality. Each ConfigState instance
// provides methods equivalent to the top-level functions.
//
// The zero value for ConfigState provides no indentation. You would typically
// want to set it to a space or a tab.
//
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
// with default settings. See the documentation of NewDefaultConfig for default
// values.
type ConfigState struct {
// Indent specifies the string to use for each indentation level. The
// global config instance that all top-level functions use set this to a
// single space by default. If you would like more indentation, you might
// set this to a tab with "\t" or perhaps two spaces with " ".
Indent string
// MaxDepth controls the maximum number of levels to descend into nested
// data structures. The default, 0, means there is no limit.
//
// NOTE: Circular data structures are properly detected, so it is not
// necessary to set this value unless you specifically want to limit deeply
// nested data structures.
MaxDepth int
// DisableMethods specifies whether or not error and Stringer interfaces are
// invoked for types that implement them.
DisableMethods bool
// DisablePointerMethods specifies whether or not to check for and invoke
// error and Stringer interfaces on types which only accept a pointer
// receiver when the current type is not a pointer.
//
// NOTE: This might be an unsafe action since calling one of these methods
// with a pointer receiver could technically mutate the value, however,
// in practice, types which choose to satisify an error or Stringer
// interface with a pointer receiver should not be mutating their state
// inside these interface methods. As a result, this option relies on
// access to the unsafe package, so it will not have any effect when
// running in environments without access to the unsafe package such as
// Google App Engine or with the "disableunsafe" build tag specified.
DisablePointerMethods bool
// ContinueOnMethod specifies whether or not recursion should continue once
// a custom error or Stringer interface is invoked. The default, false,
// means it will print the results of invoking the custom error or Stringer
// interface and return immediately instead of continuing to recurse into
// the internals of the data type.
//
// NOTE: This flag does not have any effect if method invocation is disabled
// via the DisableMethods or DisablePointerMethods options.
ContinueOnMethod bool
// SortKeys specifies map keys should be sorted before being printed. Use
// this to have a more deterministic, diffable output. Note that only
// native types (bool, int, uint, floats, uintptr and string) and types
// that support the error or Stringer interfaces (if methods are
// enabled) are supported, with other types sorted according to the
// reflect.Value.String() output which guarantees display stability.
SortKeys bool
// SpewKeys specifies that, as a last resort attempt, map keys should
// be spewed to strings and sorted by those strings. This is only
// considered if SortKeys is true.
SpewKeys bool
}
// Config is the active configuration of the top-level functions.
// The configuration can be changed by modifying the contents of spew.Config.
var Config = ConfigState{Indent: " "}
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the formatted string as a value that satisfies error. See NewFormatter
// for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, c.convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, c.convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, c.convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a Formatter interface returned by c.NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, c.convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
return fmt.Print(c.convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, c.convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
return fmt.Println(c.convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprint(a ...interface{}) string {
return fmt.Sprint(c.convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, c.convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a Formatter interface returned by c.NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintln(a ...interface{}) string {
return fmt.Sprintln(c.convertArgs(a)...)
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
c.Printf, c.Println, or c.Printf.
*/
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(c, v)
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
fdump(c, w, a...)
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by modifying the public members
of c. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func (c *ConfigState) Dump(a ...interface{}) {
fdump(c, os.Stdout, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func (c *ConfigState) Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(c, &buf, a...)
return buf.String()
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a spew Formatter interface using
// the ConfigState associated with s.
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = newFormatter(c, arg)
}
return formatters
}
// NewDefaultConfig returns a ConfigState with the following default settings.
//
// Indent: " "
// MaxDepth: 0
// DisableMethods: false
// DisablePointerMethods: false
// ContinueOnMethod: false
// SortKeys: false
func NewDefaultConfig() *ConfigState {
return &ConfigState{Indent: " "}
}

@ -1,202 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
Package spew implements a deep pretty printer for Go data structures to aid in
debugging.
A quick overview of the additional features spew provides over the built-in
printing facilities for Go data types are as follows:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output (only when using
Dump style)
There are two different approaches spew allows for dumping Go data structures:
* Dump style which prints with newlines, customizable indentation,
and additional debug information such as types and all pointer addresses
used to indirect to the final value
* A custom Formatter interface that integrates cleanly with the standard fmt
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
similar to the default %v while providing the additional functionality
outlined above and passing unsupported format verbs such as %x and %q
along to fmt
Quick Start
This section demonstrates how to quickly get started with spew. See the
sections below for further details on formatting and configuration options.
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
%#+v (adds types and pointer addresses):
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available
via the spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
The following configuration options are available:
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables.
Pointer method invocation is enabled by default.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are
supported with other types sorted according to the
reflect.Value.String() output which guarantees display
stability. Natural map order is used by default.
* SpewKeys
Specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only
considered if SortKeys is true.
Dump Usage
Simply call spew.Dump with a list of variables you want to dump:
spew.Dump(myVar1, myVar2, ...)
You may also call spew.Fdump if you would prefer to output to an arbitrary
io.Writer. For example, to dump to standard error:
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
A third option is to call spew.Sdump to get the formatted output as a string:
str := spew.Sdump(myVar1, myVar2, ...)
Sample Dump Output
See the Dump example for details on the setup of the types and variables being
shown here.
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) (len=1) {
(string) (len=3) "one": (bool) true
}
}
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
command as shown.
([]uint8) (len=32 cap=32) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
Custom Formatter
Spew provides a custom formatter that implements the fmt.Formatter interface
so that it integrates cleanly with standard fmt package printing functions. The
formatter is useful for inline printing of smaller data types similar to the
standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Custom Formatter Usage
The simplest way to make use of the spew custom formatter is to call one of the
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
functions have syntax you are most likely already familiar with:
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Println(myVar, myVar2)
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
See the Index for the full list convenience functions.
Sample Formatter Output
Double pointer to a uint8:
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
Pointer to circular struct with a uint8 field and a pointer to itself:
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
See the Printf example for details on the setup of variables being shown
here.
Errors
Since it is possible for custom Stringer/error interfaces to panic, spew
detects them and handles them internally by printing the panic information
inline with the output. Since spew is intended to provide deep pretty printing
capabilities on structures, it intentionally does not return any errors.
*/
package spew

@ -1,509 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"os"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
// uint8Type is a reflect.Type representing a uint8. It is used to
// convert cgo types to uint8 slices for hexdumping.
uint8Type = reflect.TypeOf(uint8(0))
// cCharRE is a regular expression that matches a cgo char.
// It is used to detect character arrays to hexdump them.
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
// char. It is used to detect unsigned character arrays to hexdump
// them.
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
// It is used to detect uint8_t arrays to hexdump them.
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
)
// dumpState contains information about the state of a dump operation.
type dumpState struct {
w io.Writer
depth int
pointers map[uintptr]int
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
}
// indent performs indentation according to the depth level and cs.Indent
// option.
func (d *dumpState) indent() {
if d.ignoreNextIndent {
d.ignoreNextIndent = false
return
}
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
}
// unpackValue returns values inside of non-nil interfaces when possible.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v
}
// dumpPtr handles formatting of pointers by indirecting them as necessary.
func (d *dumpState) dumpPtr(v reflect.Value) {
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range d.pointers {
if depth >= d.depth {
delete(d.pointers, k)
}
}
// Keep list of all dereferenced pointers to show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by dereferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
cycleFound = true
indirects--
break
}
d.pointers[addr] = d.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type information.
d.w.Write(openParenBytes)
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
d.w.Write([]byte(ve.Type().String()))
d.w.Write(closeParenBytes)
// Display pointer information.
if len(pointerChain) > 0 {
d.w.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
d.w.Write(pointerChainBytes)
}
printHexPtr(d.w, addr)
}
d.w.Write(closeParenBytes)
}
// Display dereferenced value.
d.w.Write(openParenBytes)
switch {
case nilFound == true:
d.w.Write(nilAngleBytes)
case cycleFound == true:
d.w.Write(circularBytes)
default:
d.ignoreNextType = true
d.dump(ve)
}
d.w.Write(closeParenBytes)
}
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
// reflection) arrays and slices are dumped in hexdump -C fashion.
func (d *dumpState) dumpSlice(v reflect.Value) {
// Determine whether this type should be hex dumped or not. Also,
// for types which should be hexdumped, try to use the underlying data
// first, then fall back to trying to convert them to a uint8 slice.
var buf []uint8
doConvert := false
doHexDump := false
numEntries := v.Len()
if numEntries > 0 {
vt := v.Index(0).Type()
vts := vt.String()
switch {
// C types that need to be converted.
case cCharRE.MatchString(vts):
fallthrough
case cUnsignedCharRE.MatchString(vts):
fallthrough
case cUint8tCharRE.MatchString(vts):
doConvert = true
// Try to use existing uint8 slices and fall back to converting
// and copying if that fails.
case vt.Kind() == reflect.Uint8:
// We need an addressable interface to convert the type
// to a byte slice. However, the reflect package won't
// give us an interface on certain things like
// unexported struct fields in order to enforce
// visibility rules. We use unsafe, when available, to
// bypass these restrictions since this package does not
// mutate the values.
vs := v
if !vs.CanInterface() || !vs.CanAddr() {
vs = unsafeReflectValue(vs)
}
if !UnsafeDisabled {
vs = vs.Slice(0, numEntries)
// Use the existing uint8 slice if it can be
// type asserted.
iface := vs.Interface()
if slice, ok := iface.([]uint8); ok {
buf = slice
doHexDump = true
break
}
}
// The underlying data needs to be converted if it can't
// be type asserted to a uint8 slice.
doConvert = true
}
// Copy and convert the underlying type if needed.
if doConvert && vt.ConvertibleTo(uint8Type) {
// Convert and copy each element into a uint8 byte
// slice.
buf = make([]uint8, numEntries)
for i := 0; i < numEntries; i++ {
vv := v.Index(i)
buf[i] = uint8(vv.Convert(uint8Type).Uint())
}
doHexDump = true
}
}
// Hexdump the entire slice as needed.
if doHexDump {
indent := strings.Repeat(d.cs.Indent, d.depth)
str := indent + hex.Dump(buf)
str = strings.Replace(str, "\n", "\n"+indent, -1)
str = strings.TrimRight(str, d.cs.Indent)
d.w.Write([]byte(str))
return
}
// Recursively call dump for each item.
for i := 0; i < numEntries; i++ {
d.dump(d.unpackValue(v.Index(i)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
// dump is the main workhorse for dumping a value. It uses the passed reflect
// value to figure out what kind of object we are dealing with and formats it
// appropriately. It is a recursive function, however circular data structures
// are detected and handled properly.
func (d *dumpState) dump(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
d.w.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
d.indent()
d.dumpPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !d.ignoreNextType {
d.indent()
d.w.Write(openParenBytes)
d.w.Write([]byte(v.Type().String()))
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
d.ignoreNextType = false
// Display length and capacity if the built-in len and cap functions
// work with the value's kind and the len/cap itself is non-zero.
valueLen, valueCap := 0, 0
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Chan:
valueLen, valueCap = v.Len(), v.Cap()
case reflect.Map, reflect.String:
valueLen = v.Len()
}
if valueLen != 0 || valueCap != 0 {
d.w.Write(openParenBytes)
if valueLen != 0 {
d.w.Write(lenEqualsBytes)
printInt(d.w, int64(valueLen), 10)
}
if valueCap != 0 {
if valueLen != 0 {
d.w.Write(spaceBytes)
}
d.w.Write(capEqualsBytes)
printInt(d.w, int64(valueCap), 10)
}
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
// Call Stringer/error interfaces if they exist and the handle methods flag
// is enabled
if !d.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(d.cs, d.w, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(d.w, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(d.w, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(d.w, v.Uint(), 10)
case reflect.Float32:
printFloat(d.w, v.Float(), 32)
case reflect.Float64:
printFloat(d.w, v.Float(), 64)
case reflect.Complex64:
printComplex(d.w, v.Complex(), 32)
case reflect.Complex128:
printComplex(d.w, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
d.dumpSlice(v)
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.String:
d.w.Write([]byte(strconv.Quote(v.String())))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
d.w.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
numEntries := v.Len()
keys := v.MapKeys()
if d.cs.SortKeys {
sortValues(keys, d.cs)
}
for i, key := range keys {
d.dump(d.unpackValue(key))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.MapIndex(key)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Struct:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
vt := v.Type()
numFields := v.NumField()
for i := 0; i < numFields; i++ {
d.indent()
vtf := vt.Field(i)
d.w.Write([]byte(vtf.Name))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.Field(i)))
if i < (numFields - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(d.w, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(d.w, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it in case any new
// types are added.
default:
if v.CanInterface() {
fmt.Fprintf(d.w, "%v", v.Interface())
} else {
fmt.Fprintf(d.w, "%v", v.String())
}
}
}
// fdump is a helper function to consolidate the logic from the various public
// methods which take varying writers and config states.
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
for _, arg := range a {
if arg == nil {
w.Write(interfaceBytes)
w.Write(spaceBytes)
w.Write(nilAngleBytes)
w.Write(newlineBytes)
continue
}
d := dumpState{w: w, cs: cs}
d.pointers = make(map[uintptr]int)
d.dump(reflect.ValueOf(arg))
d.w.Write(newlineBytes)
}
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func Fdump(w io.Writer, a ...interface{}) {
fdump(&Config, w, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(&Config, &buf, a...)
return buf.String()
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by an exported package global,
spew.Config. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func Dump(a ...interface{}) {
fdump(&Config, os.Stdout, a...)
}

File diff suppressed because it is too large Load Diff

@ -1,98 +0,0 @@
// Copyright (c) 2013 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when both cgo is supported and "-tags testcgo" is added to the go test
// command line. This means the cgo tests are only added (and hence run) when
// specifially requested. This configuration is used because spew itself
// does not require cgo to run even though it does handle certain cgo types
// specially. Rather than forcing all clients to require cgo and an external
// C compiler just to run the tests, this scheme makes them optional.
// +build cgo,testcgo
package spew_test
import (
"fmt"
"github.com/davecgh/go-spew/spew/testdata"
)
func addCgoDumpTests() {
// C char pointer.
v := testdata.GetCgoCharPointer()
nv := testdata.GetCgoNullCharPointer()
pv := &v
vcAddr := fmt.Sprintf("%p", v)
vAddr := fmt.Sprintf("%p", pv)
pvAddr := fmt.Sprintf("%p", &pv)
vt := "*testdata._Ctype_char"
vs := "116"
addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n")
addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n")
addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n")
addDumpTest(nv, "("+vt+")(<nil>)\n")
// C char array.
v2, v2l, v2c := testdata.GetCgoCharArray()
v2Len := fmt.Sprintf("%d", v2l)
v2Cap := fmt.Sprintf("%d", v2c)
v2t := "[6]testdata._Ctype_char"
v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " +
"{\n 00000000 74 65 73 74 32 00 " +
" |test2.|\n}"
addDumpTest(v2, "("+v2t+") "+v2s+"\n")
// C unsigned char array.
v3, v3l, v3c := testdata.GetCgoUnsignedCharArray()
v3Len := fmt.Sprintf("%d", v3l)
v3Cap := fmt.Sprintf("%d", v3c)
v3t := "[6]testdata._Ctype_unsignedchar"
v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " +
"{\n 00000000 74 65 73 74 33 00 " +
" |test3.|\n}"
addDumpTest(v3, "("+v3t+") "+v3s+"\n")
// C signed char array.
v4, v4l, v4c := testdata.GetCgoSignedCharArray()
v4Len := fmt.Sprintf("%d", v4l)
v4Cap := fmt.Sprintf("%d", v4c)
v4t := "[6]testdata._Ctype_schar"
v4t2 := "testdata._Ctype_schar"
v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 +
") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 +
") 0\n}"
addDumpTest(v4, "("+v4t+") "+v4s+"\n")
// C uint8_t array.
v5, v5l, v5c := testdata.GetCgoUint8tArray()
v5Len := fmt.Sprintf("%d", v5l)
v5Cap := fmt.Sprintf("%d", v5c)
v5t := "[6]testdata._Ctype_uint8_t"
v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " +
"{\n 00000000 74 65 73 74 35 00 " +
" |test5.|\n}"
addDumpTest(v5, "("+v5t+") "+v5s+"\n")
// C typedefed unsigned char array.
v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray()
v6Len := fmt.Sprintf("%d", v6l)
v6Cap := fmt.Sprintf("%d", v6c)
v6t := "[6]testdata._Ctype_custom_uchar_t"
v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " +
"{\n 00000000 74 65 73 74 36 00 " +
" |test6.|\n}"
addDumpTest(v6, "("+v6t+") "+v6s+"\n")
}

@ -1,26 +0,0 @@
// Copyright (c) 2013 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when either cgo is not supported or "-tags testcgo" is not added to the go
// test command line. This file intentionally does not setup any cgo tests in
// this scenario.
// +build !cgo !testcgo
package spew_test
func addCgoDumpTests() {
// Don't add any tests for cgo since this file is only compiled when
// there should not be any cgo tests.
}

@ -1,226 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew_test
import (
"fmt"
"github.com/davecgh/go-spew/spew"
)
type Flag int
const (
flagOne Flag = iota
flagTwo
)
var flagStrings = map[Flag]string{
flagOne: "flagOne",
flagTwo: "flagTwo",
}
func (f Flag) String() string {
if s, ok := flagStrings[f]; ok {
return s
}
return fmt.Sprintf("Unknown flag (%d)", int(f))
}
type Bar struct {
data uintptr
}
type Foo struct {
unexportedField Bar
ExportedField map[interface{}]interface{}
}
// This example demonstrates how to use Dump to dump variables to stdout.
func ExampleDump() {
// The following package level declarations are assumed for this example:
/*
type Flag int
const (
flagOne Flag = iota
flagTwo
)
var flagStrings = map[Flag]string{
flagOne: "flagOne",
flagTwo: "flagTwo",
}
func (f Flag) String() string {
if s, ok := flagStrings[f]; ok {
return s
}
return fmt.Sprintf("Unknown flag (%d)", int(f))
}
type Bar struct {
data uintptr
}
type Foo struct {
unexportedField Bar
ExportedField map[interface{}]interface{}
}
*/
// Setup some sample data structures for the example.
bar := Bar{uintptr(0)}
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
f := Flag(5)
b := []byte{
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
0x31, 0x32,
}
// Dump!
spew.Dump(s1, f, b)
// Output:
// (spew_test.Foo) {
// unexportedField: (spew_test.Bar) {
// data: (uintptr) <nil>
// },
// ExportedField: (map[interface {}]interface {}) (len=1) {
// (string) (len=3) "one": (bool) true
// }
// }
// (spew_test.Flag) Unknown flag (5)
// ([]uint8) (len=34 cap=34) {
// 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
// 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
// 00000020 31 32 |12|
// }
//
}
// This example demonstrates how to use Printf to display a variable with a
// format string and inline formatting.
func ExamplePrintf() {
// Create a double pointer to a uint 8.
ui8 := uint8(5)
pui8 := &ui8
ppui8 := &pui8
// Create a circular data type.
type circular struct {
ui8 uint8
c *circular
}
c := circular{ui8: 1}
c.c = &c
// Print!
spew.Printf("ppui8: %v\n", ppui8)
spew.Printf("circular: %v\n", c)
// Output:
// ppui8: <**>5
// circular: {1 <*>{1 <*><shown>}}
}
// This example demonstrates how to use a ConfigState.
func ExampleConfigState() {
// Modify the indent level of the ConfigState only. The global
// configuration is not modified.
scs := spew.ConfigState{Indent: "\t"}
// Output using the ConfigState instance.
v := map[string]int{"one": 1}
scs.Printf("v: %v\n", v)
scs.Dump(v)
// Output:
// v: map[one:1]
// (map[string]int) (len=1) {
// (string) (len=3) "one": (int) 1
// }
}
// This example demonstrates how to use ConfigState.Dump to dump variables to
// stdout
func ExampleConfigState_Dump() {
// See the top-level Dump example for details on the types used in this
// example.
// Create two ConfigState instances with different indentation.
scs := spew.ConfigState{Indent: "\t"}
scs2 := spew.ConfigState{Indent: " "}
// Setup some sample data structures for the example.
bar := Bar{uintptr(0)}
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
// Dump using the ConfigState instances.
scs.Dump(s1)
scs2.Dump(s1)
// Output:
// (spew_test.Foo) {
// unexportedField: (spew_test.Bar) {
// data: (uintptr) <nil>
// },
// ExportedField: (map[interface {}]interface {}) (len=1) {
// (string) (len=3) "one": (bool) true
// }
// }
// (spew_test.Foo) {
// unexportedField: (spew_test.Bar) {
// data: (uintptr) <nil>
// },
// ExportedField: (map[interface {}]interface {}) (len=1) {
// (string) (len=3) "one": (bool) true
// }
// }
//
}
// This example demonstrates how to use ConfigState.Printf to display a variable
// with a format string and inline formatting.
func ExampleConfigState_Printf() {
// See the top-level Dump example for details on the types used in this
// example.
// Create two ConfigState instances and modify the method handling of the
// first ConfigState only.
scs := spew.NewDefaultConfig()
scs2 := spew.NewDefaultConfig()
scs.DisableMethods = true
// Alternatively
// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
// scs2 := spew.ConfigState{Indent: " "}
// This is of type Flag which implements a Stringer and has raw value 1.
f := flagTwo
// Dump using the ConfigState instances.
scs.Printf("f: %v\n", f)
scs2.Printf("f: %v\n", f)
// Output:
// f: 1
// f: flagTwo
}

@ -1,419 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
)
// supportedFlags is a list of all the character flags supported by fmt package.
const supportedFlags = "0-+# "
// formatState implements the fmt.Formatter interface and contains information
// about the state of a formatting operation. The NewFormatter function can
// be used to get a new Formatter which can be used directly as arguments
// in standard fmt package printing calls.
type formatState struct {
value interface{}
fs fmt.State
depth int
pointers map[uintptr]int
ignoreNextType bool
cs *ConfigState
}
// buildDefaultFormat recreates the original format string without precision
// and width information to pass in to fmt.Sprintf in the case of an
// unrecognized type. Unless new types are added to the language, this
// function won't ever be called.
func (f *formatState) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
buf.WriteRune('v')
format = buf.String()
return format
}
// constructOrigFormat recreates the original format string including precision
// and width information to pass along to the standard fmt package. This allows
// automatic deferral of all format strings this package doesn't support.
func (f *formatState) constructOrigFormat(verb rune) (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
if width, ok := f.fs.Width(); ok {
buf.WriteString(strconv.Itoa(width))
}
if precision, ok := f.fs.Precision(); ok {
buf.Write(precisionBytes)
buf.WriteString(strconv.Itoa(precision))
}
buf.WriteRune(verb)
format = buf.String()
return format
}
// unpackValue returns values inside of non-nil interfaces when possible and
// ensures that types for values which have been unpacked from an interface
// are displayed when the show types flag is also set.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
f.ignoreNextType = false
if !v.IsNil() {
v = v.Elem()
}
}
return v
}
// formatPtr handles formatting of pointers by indirecting them as necessary.
func (f *formatState) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil.
showTypes := f.fs.Flag('#')
if v.IsNil() && (!showTypes || f.ignoreNextType) {
f.fs.Write(nilAngleBytes)
return
}
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range f.pointers {
if depth >= f.depth {
delete(f.pointers, k)
}
}
// Keep list of all dereferenced pointers to possibly show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by derferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
cycleFound = true
indirects--
break
}
f.pointers[addr] = f.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type or indirection level depending on flags.
if showTypes && !f.ignoreNextType {
f.fs.Write(openParenBytes)
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
f.fs.Write([]byte(ve.Type().String()))
f.fs.Write(closeParenBytes)
} else {
if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*")
}
f.fs.Write(openAngleBytes)
f.fs.Write([]byte(strings.Repeat("*", indirects)))
f.fs.Write(closeAngleBytes)
}
// Display pointer information depending on flags.
if f.fs.Flag('+') && (len(pointerChain) > 0) {
f.fs.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
f.fs.Write(pointerChainBytes)
}
printHexPtr(f.fs, addr)
}
f.fs.Write(closeParenBytes)
}
// Display dereferenced value.
switch {
case nilFound == true:
f.fs.Write(nilAngleBytes)
case cycleFound == true:
f.fs.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
}
}
// format is the main workhorse for providing the Formatter interface. It
// uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly.
func (f *formatState) format(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
f.fs.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
f.formatPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !f.ignoreNextType && f.fs.Flag('#') {
f.fs.Write(openParenBytes)
f.fs.Write([]byte(v.Type().String()))
f.fs.Write(closeParenBytes)
}
f.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if !f.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.cs, f.fs, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(f.fs, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(f.fs, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(f.fs, v.Uint(), 10)
case reflect.Float32:
printFloat(f.fs, v.Float(), 32)
case reflect.Float64:
printFloat(f.fs, v.Float(), 64)
case reflect.Complex64:
printComplex(f.fs, v.Complex(), 32)
case reflect.Complex128:
printComplex(f.fs, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
f.fs.Write(openBracketBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(v.Index(i)))
}
}
f.depth--
f.fs.Write(closeBracketBytes)
case reflect.String:
f.fs.Write([]byte(v.String()))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
f.fs.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
f.fs.Write(openMapBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
keys := v.MapKeys()
if f.cs.SortKeys {
sortValues(keys, f.cs)
}
for i, key := range keys {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(key))
f.fs.Write(colonBytes)
f.ignoreNextType = true
f.format(f.unpackValue(v.MapIndex(key)))
}
}
f.depth--
f.fs.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
f.fs.Write(openBraceBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
vt := v.Type()
for i := 0; i < numFields; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
vtf := vt.Field(i)
if f.fs.Flag('+') || f.fs.Flag('#') {
f.fs.Write([]byte(vtf.Name))
f.fs.Write(colonBytes)
}
f.format(f.unpackValue(v.Field(i)))
}
}
f.depth--
f.fs.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(f.fs, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(f.fs, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added.
default:
format := f.buildDefaultFormat()
if v.CanInterface() {
fmt.Fprintf(f.fs, format, v.Interface())
} else {
fmt.Fprintf(f.fs, format, v.String())
}
}
}
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
// details.
func (f *formatState) Format(fs fmt.State, verb rune) {
f.fs = fs
// Use standard formatting for verbs that are not v.
if verb != 'v' {
format := f.constructOrigFormat(verb)
fmt.Fprintf(fs, format, f.value)
return
}
if f.value == nil {
if fs.Flag('#') {
fs.Write(interfaceBytes)
}
fs.Write(nilAngleBytes)
return
}
f.format(reflect.ValueOf(f.value))
}
// newFormatter is a helper function to consolidate the logic from the various
// public methods which take varying config states.
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
fs := &formatState{value: v, cs: cs}
fs.pointers = make(map[uintptr]int)
return fs
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
Printf, Println, or Fprintf.
*/
func NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(&Config, v)
}

File diff suppressed because it is too large Load Diff

@ -1,87 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
This test file is part of the spew package rather than than the spew_test
package because it needs access to internals to properly test certain cases
which are not possible via the public interface since they should never happen.
*/
package spew
import (
"bytes"
"reflect"
"testing"
)
// dummyFmtState implements a fake fmt.State to use for testing invalid
// reflect.Value handling. This is necessary because the fmt package catches
// invalid values before invoking the formatter on them.
type dummyFmtState struct {
bytes.Buffer
}
func (dfs *dummyFmtState) Flag(f int) bool {
if f == int('+') {
return true
}
return false
}
func (dfs *dummyFmtState) Precision() (int, bool) {
return 0, false
}
func (dfs *dummyFmtState) Width() (int, bool) {
return 0, false
}
// TestInvalidReflectValue ensures the dump and formatter code handles an
// invalid reflect value properly. This needs access to internal state since it
// should never happen in real code and therefore can't be tested via the public
// API.
func TestInvalidReflectValue(t *testing.T) {
i := 1
// Dump invalid reflect value.
v := new(reflect.Value)
buf := new(bytes.Buffer)
d := dumpState{w: buf, cs: &Config}
d.dump(*v)
s := buf.String()
want := "<invalid>"
if s != want {
t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want)
}
i++
// Formatter invalid reflect value.
buf2 := new(dummyFmtState)
f := formatState{value: *v, cs: &Config, fs: buf2}
f.format(*v)
s = buf2.String()
want = "<invalid>"
if s != want {
t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want)
}
}
// SortValues makes the internal sortValues function available to the test
// package.
func SortValues(values []reflect.Value, cs *ConfigState) {
sortValues(values, cs)
}

@ -1,101 +0,0 @@
// Copyright (c) 2013-2015 Dave Collins <dave@davec.name>
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine and "-tags disableunsafe"
// is not added to the go build command line.
// +build !appengine,!disableunsafe
/*
This test file is part of the spew package rather than than the spew_test
package because it needs access to internals to properly test certain cases
which are not possible via the public interface since they should never happen.
*/
package spew
import (
"bytes"
"reflect"
"testing"
"unsafe"
)
// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
// the maximum kind value which does not exist. This is needed to test the
// fallback code which punts to the standard fmt library for new types that
// might get added to the language.
func changeKind(v *reflect.Value, readOnly bool) {
rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag))
*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift)
if readOnly {
*rvf |= flagRO
} else {
*rvf &= ^uintptr(flagRO)
}
}
// TestAddedReflectValue tests functionaly of the dump and formatter code which
// falls back to the standard fmt library for new types that might get added to
// the language.
func TestAddedReflectValue(t *testing.T) {
i := 1
// Dump using a reflect.Value that is exported.
v := reflect.ValueOf(int8(5))
changeKind(&v, false)
buf := new(bytes.Buffer)
d := dumpState{w: buf, cs: &Config}
d.dump(v)
s := buf.String()
want := "(int8) 5"
if s != want {
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
}
i++
// Dump using a reflect.Value that is not exported.
changeKind(&v, true)
buf.Reset()
d.dump(v)
s = buf.String()
want = "(int8) <int8 Value>"
if s != want {
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
}
i++
// Formatter using a reflect.Value that is exported.
changeKind(&v, false)
buf2 := new(dummyFmtState)
f := formatState{value: v, cs: &Config, fs: buf2}
f.format(v)
s = buf2.String()
want = "5"
if s != want {
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
}
i++
// Formatter using a reflect.Value that is not exported.
changeKind(&v, true)
buf2.Reset()
f = formatState{value: v, cs: &Config, fs: buf2}
f.format(v)
s = buf2.String()
want = "<int8 Value>"
if s != want {
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
}
}

@ -1,148 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"fmt"
"io"
)
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the formatted string as a value that satisfies error. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a default Formatter interface returned by NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
func Print(a ...interface{}) (n int, err error) {
return fmt.Print(convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
func Println(a ...interface{}) (n int, err error) {
return fmt.Println(convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprint(a ...interface{}) string {
return fmt.Sprint(convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintln(a ...interface{}) string {
return fmt.Sprintln(convertArgs(a)...)
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a default spew Formatter interface.
func convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = NewFormatter(arg)
}
return formatters
}

@ -1,309 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/davecgh/go-spew/spew"
)
// spewFunc is used to identify which public function of the spew package or
// ConfigState a test applies to.
type spewFunc int
const (
fCSFdump spewFunc = iota
fCSFprint
fCSFprintf
fCSFprintln
fCSPrint
fCSPrintln
fCSSdump
fCSSprint
fCSSprintf
fCSSprintln
fCSErrorf
fCSNewFormatter
fErrorf
fFprint
fFprintln
fPrint
fPrintln
fSdump
fSprint
fSprintf
fSprintln
)
// Map of spewFunc values to names for pretty printing.
var spewFuncStrings = map[spewFunc]string{
fCSFdump: "ConfigState.Fdump",
fCSFprint: "ConfigState.Fprint",
fCSFprintf: "ConfigState.Fprintf",
fCSFprintln: "ConfigState.Fprintln",
fCSSdump: "ConfigState.Sdump",
fCSPrint: "ConfigState.Print",
fCSPrintln: "ConfigState.Println",
fCSSprint: "ConfigState.Sprint",
fCSSprintf: "ConfigState.Sprintf",
fCSSprintln: "ConfigState.Sprintln",
fCSErrorf: "ConfigState.Errorf",
fCSNewFormatter: "ConfigState.NewFormatter",
fErrorf: "spew.Errorf",
fFprint: "spew.Fprint",
fFprintln: "spew.Fprintln",
fPrint: "spew.Print",
fPrintln: "spew.Println",
fSdump: "spew.Sdump",
fSprint: "spew.Sprint",
fSprintf: "spew.Sprintf",
fSprintln: "spew.Sprintln",
}
func (f spewFunc) String() string {
if s, ok := spewFuncStrings[f]; ok {
return s
}
return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
}
// spewTest is used to describe a test to be performed against the public
// functions of the spew package or ConfigState.
type spewTest struct {
cs *spew.ConfigState
f spewFunc
format string
in interface{}
want string
}
// spewTests houses the tests to be performed against the public functions of
// the spew package and ConfigState.
//
// These tests are only intended to ensure the public functions are exercised
// and are intentionally not exhaustive of types. The exhaustive type
// tests are handled in the dump and format tests.
var spewTests []spewTest
// redirStdout is a helper function to return the standard output from f as a
// byte slice.
func redirStdout(f func()) ([]byte, error) {
tempFile, err := ioutil.TempFile("", "ss-test")
if err != nil {
return nil, err
}
fileName := tempFile.Name()
defer os.Remove(fileName) // Ignore error
origStdout := os.Stdout
os.Stdout = tempFile
f()
os.Stdout = origStdout
tempFile.Close()
return ioutil.ReadFile(fileName)
}
func initSpewTests() {
// Config states with various settings.
scsDefault := spew.NewDefaultConfig()
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
// Variables for tests on types which implement Stringer interface with and
// without a pointer receiver.
ts := stringer("test")
tps := pstringer("test")
// depthTester is used to test max depth handling for structs, array, slices
// and maps.
type depthTester struct {
ic indirCir1
arr [1]string
slice []string
m map[string]int
}
dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
map[string]int{"one": 1}}
// Variable for tests on types which implement error interface.
te := customError(10)
spewTests = []spewTest{
{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
{scsDefault, fCSFprint, "", int16(32767), "32767"},
{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
{scsDefault, fFprint, "", float32(3.14), "3.14"},
{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
{scsDefault, fPrint, "", true, "true"},
{scsDefault, fPrintln, "", false, "false\n"},
{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
{scsNoMethods, fCSFprint, "", ts, "test"},
{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
{scsNoMethods, fCSFprint, "", tps, "test"},
{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
{scsNoPmethods, fCSFprint, "", tps, "test"},
{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
" ic: (spew_test.indirCir1) {\n <max depth reached>\n },\n" +
" arr: ([1]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
" slice: ([]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
" m: (map[string]int) (len=1) {\n <max depth reached>\n }\n}\n"},
{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
"(len=4) (stringer test) \"test\"\n"},
{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
"(error: 10) 10\n"},
}
}
// TestSpew executes all of the tests described by spewTests.
func TestSpew(t *testing.T) {
initSpewTests()
t.Logf("Running %d tests", len(spewTests))
for i, test := range spewTests {
buf := new(bytes.Buffer)
switch test.f {
case fCSFdump:
test.cs.Fdump(buf, test.in)
case fCSFprint:
test.cs.Fprint(buf, test.in)
case fCSFprintf:
test.cs.Fprintf(buf, test.format, test.in)
case fCSFprintln:
test.cs.Fprintln(buf, test.in)
case fCSPrint:
b, err := redirStdout(func() { test.cs.Print(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fCSPrintln:
b, err := redirStdout(func() { test.cs.Println(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fCSSdump:
str := test.cs.Sdump(test.in)
buf.WriteString(str)
case fCSSprint:
str := test.cs.Sprint(test.in)
buf.WriteString(str)
case fCSSprintf:
str := test.cs.Sprintf(test.format, test.in)
buf.WriteString(str)
case fCSSprintln:
str := test.cs.Sprintln(test.in)
buf.WriteString(str)
case fCSErrorf:
err := test.cs.Errorf(test.format, test.in)
buf.WriteString(err.Error())
case fCSNewFormatter:
fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
case fErrorf:
err := spew.Errorf(test.format, test.in)
buf.WriteString(err.Error())
case fFprint:
spew.Fprint(buf, test.in)
case fFprintln:
spew.Fprintln(buf, test.in)
case fPrint:
b, err := redirStdout(func() { spew.Print(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fPrintln:
b, err := redirStdout(func() { spew.Println(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fSdump:
str := spew.Sdump(test.in)
buf.WriteString(str)
case fSprint:
str := spew.Sprint(test.in)
buf.WriteString(str)
case fSprintf:
str := spew.Sprintf(test.format, test.in)
buf.WriteString(str)
case fSprintln:
str := spew.Sprintln(test.in)
buf.WriteString(str)
default:
t.Errorf("%v #%d unrecognized function", test.f, i)
continue
}
s := buf.String()
if test.want != s {
t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
continue
}
}
}

@ -1,82 +0,0 @@
// Copyright (c) 2013 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when both cgo is supported and "-tags testcgo" is added to the go test
// command line. This code should really only be in the dumpcgo_test.go file,
// but unfortunately Go will not allow cgo in test files, so this is a
// workaround to allow cgo types to be tested. This configuration is used
// because spew itself does not require cgo to run even though it does handle
// certain cgo types specially. Rather than forcing all clients to require cgo
// and an external C compiler just to run the tests, this scheme makes them
// optional.
// +build cgo,testcgo
package testdata
/*
#include <stdint.h>
typedef unsigned char custom_uchar_t;
char *ncp = 0;
char *cp = "test";
char ca[6] = {'t', 'e', 's', 't', '2', '\0'};
unsigned char uca[6] = {'t', 'e', 's', 't', '3', '\0'};
signed char sca[6] = {'t', 'e', 's', 't', '4', '\0'};
uint8_t ui8ta[6] = {'t', 'e', 's', 't', '5', '\0'};
custom_uchar_t tuca[6] = {'t', 'e', 's', 't', '6', '\0'};
*/
import "C"
// GetCgoNullCharPointer returns a null char pointer via cgo. This is only
// used for tests.
func GetCgoNullCharPointer() interface{} {
return C.ncp
}
// GetCgoCharPointer returns a char pointer via cgo. This is only used for
// tests.
func GetCgoCharPointer() interface{} {
return C.cp
}
// GetCgoCharArray returns a char array via cgo and the array's len and cap.
// This is only used for tests.
func GetCgoCharArray() (interface{}, int, int) {
return C.ca, len(C.ca), cap(C.ca)
}
// GetCgoUnsignedCharArray returns an unsigned char array via cgo and the
// array's len and cap. This is only used for tests.
func GetCgoUnsignedCharArray() (interface{}, int, int) {
return C.uca, len(C.uca), cap(C.uca)
}
// GetCgoSignedCharArray returns a signed char array via cgo and the array's len
// and cap. This is only used for tests.
func GetCgoSignedCharArray() (interface{}, int, int) {
return C.sca, len(C.sca), cap(C.sca)
}
// GetCgoUint8tArray returns a uint8_t array via cgo and the array's len and
// cap. This is only used for tests.
func GetCgoUint8tArray() (interface{}, int, int) {
return C.ui8ta, len(C.ui8ta), cap(C.ui8ta)
}
// GetCgoTypdefedUnsignedCharArray returns a typedefed unsigned char array via
// cgo and the array's len and cap. This is only used for tests.
func GetCgoTypdefedUnsignedCharArray() (interface{}, int, int) {
return C.tuca, len(C.tuca), cap(C.tuca)
}

@ -1,61 +0,0 @@
github.com/davecgh/go-spew/spew/dump.go dumpState.dump 100.00% (88/88)
github.com/davecgh/go-spew/spew/format.go formatState.format 100.00% (82/82)
github.com/davecgh/go-spew/spew/format.go formatState.formatPtr 100.00% (52/52)
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpPtr 100.00% (44/44)
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpSlice 100.00% (39/39)
github.com/davecgh/go-spew/spew/common.go handleMethods 100.00% (30/30)
github.com/davecgh/go-spew/spew/common.go printHexPtr 100.00% (18/18)
github.com/davecgh/go-spew/spew/common.go unsafeReflectValue 100.00% (13/13)
github.com/davecgh/go-spew/spew/format.go formatState.constructOrigFormat 100.00% (12/12)
github.com/davecgh/go-spew/spew/dump.go fdump 100.00% (11/11)
github.com/davecgh/go-spew/spew/format.go formatState.Format 100.00% (11/11)
github.com/davecgh/go-spew/spew/common.go init 100.00% (10/10)
github.com/davecgh/go-spew/spew/common.go printComplex 100.00% (9/9)
github.com/davecgh/go-spew/spew/common.go valuesSorter.Less 100.00% (8/8)
github.com/davecgh/go-spew/spew/format.go formatState.buildDefaultFormat 100.00% (7/7)
github.com/davecgh/go-spew/spew/format.go formatState.unpackValue 100.00% (5/5)
github.com/davecgh/go-spew/spew/dump.go dumpState.indent 100.00% (4/4)
github.com/davecgh/go-spew/spew/common.go catchPanic 100.00% (4/4)
github.com/davecgh/go-spew/spew/config.go ConfigState.convertArgs 100.00% (4/4)
github.com/davecgh/go-spew/spew/spew.go convertArgs 100.00% (4/4)
github.com/davecgh/go-spew/spew/format.go newFormatter 100.00% (3/3)
github.com/davecgh/go-spew/spew/dump.go Sdump 100.00% (3/3)
github.com/davecgh/go-spew/spew/common.go printBool 100.00% (3/3)
github.com/davecgh/go-spew/spew/common.go sortValues 100.00% (3/3)
github.com/davecgh/go-spew/spew/config.go ConfigState.Sdump 100.00% (3/3)
github.com/davecgh/go-spew/spew/dump.go dumpState.unpackValue 100.00% (3/3)
github.com/davecgh/go-spew/spew/spew.go Printf 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Println 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Sprint 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Sprintf 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Sprintln 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go printFloat 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go NewDefaultConfig 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go printInt 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go printUint 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go valuesSorter.Len 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go valuesSorter.Swap 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Errorf 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprint 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintf 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintln 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Print 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Printf 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Println 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprint 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintf 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintln 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.NewFormatter 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Fdump 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Dump 100.00% (1/1)
github.com/davecgh/go-spew/spew/dump.go Fdump 100.00% (1/1)
github.com/davecgh/go-spew/spew/dump.go Dump 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Fprintln 100.00% (1/1)
github.com/davecgh/go-spew/spew/format.go NewFormatter 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Errorf 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Fprint 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Fprintf 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Print 100.00% (1/1)
github.com/davecgh/go-spew/spew ------------------------------- 100.00% (505/505)

@ -1,5 +0,0 @@
language: go
go:
- 1.5
- tip

@ -1,27 +0,0 @@
Copyright (c) 2013, Patrick Mezard
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -1,50 +0,0 @@
go-difflib
==========
[![Build Status](https://travis-ci.org/pmezard/go-difflib.png?branch=master)](https://travis-ci.org/pmezard/go-difflib)
[![GoDoc](https://godoc.org/github.com/pmezard/go-difflib/difflib?status.svg)](https://godoc.org/github.com/pmezard/go-difflib/difflib)
Go-difflib is a partial port of python 3 difflib package. Its main goal
was to make unified and context diff available in pure Go, mostly for
testing purposes.
The following class and functions (and related tests) have be ported:
* `SequenceMatcher`
* `unified_diff()`
* `context_diff()`
## Installation
```bash
$ go get github.com/pmezard/go-difflib/difflib
```
### Quick Start
Diffs are configured with Unified (or ContextDiff) structures, and can
be output to an io.Writer or returned as a string.
```Go
diff := UnifiedDiff{
A: difflib.SplitLines("foo\nbar\n"),
B: difflib.SplitLines("foo\nbaz\n"),
FromFile: "Original",
ToFile: "Current",
Context: 3,
}
text, _ := GetUnifiedDiffString(diff)
fmt.Printf(text)
```
would output:
```
--- Original
+++ Current
@@ -1,3 +1,3 @@
foo
-bar
+baz
```

@ -1,758 +0,0 @@
// Package difflib is a partial port of Python difflib module.
//
// It provides tools to compare sequences of strings and generate textual diffs.
//
// The following class and functions have been ported:
//
// - SequenceMatcher
//
// - unified_diff
//
// - context_diff
//
// Getting unified diffs was the main goal of the port. Keep in mind this code
// is mostly suitable to output text differences in a human friendly way, there
// are no guarantees generated diffs are consumable by patch(1).
package difflib
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func calculateRatio(matches, length int) float64 {
if length > 0 {
return 2.0 * float64(matches) / float64(length)
}
return 1.0
}
type Match struct {
A int
B int
Size int
}
type OpCode struct {
Tag byte
I1 int
I2 int
J1 int
J2 int
}
// SequenceMatcher compares sequence of strings. The basic
// algorithm predates, and is a little fancier than, an algorithm
// published in the late 1980's by Ratcliff and Obershelp under the
// hyperbolic name "gestalt pattern matching". The basic idea is to find
// the longest contiguous matching subsequence that contains no "junk"
// elements (R-O doesn't address junk). The same idea is then applied
// recursively to the pieces of the sequences to the left and to the right
// of the matching subsequence. This does not yield minimal edit
// sequences, but does tend to yield matches that "look right" to people.
//
// SequenceMatcher tries to compute a "human-friendly diff" between two
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
// longest *contiguous* & junk-free matching subsequence. That's what
// catches peoples' eyes. The Windows(tm) windiff has another interesting
// notion, pairing up elements that appear uniquely in each sequence.
// That, and the method here, appear to yield more intuitive difference
// reports than does diff. This method appears to be the least vulnerable
// to synching up on blocks of "junk lines", though (like blank lines in
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
// because this is the only method of the 3 that has a *concept* of
// "junk" <wink>.
//
// Timing: Basic R-O is cubic time worst case and quadratic time expected
// case. SequenceMatcher is quadratic time for the worst case and has
// expected-case behavior dependent in a complicated way on how many
// elements the sequences have in common; best case time is linear.
type SequenceMatcher struct {
a []string
b []string
b2j map[string][]int
IsJunk func(string) bool
autoJunk bool
bJunk map[string]struct{}
matchingBlocks []Match
fullBCount map[string]int
bPopular map[string]struct{}
opCodes []OpCode
}
func NewMatcher(a, b []string) *SequenceMatcher {
m := SequenceMatcher{autoJunk: true}
m.SetSeqs(a, b)
return &m
}
func NewMatcherWithJunk(a, b []string, autoJunk bool,
isJunk func(string) bool) *SequenceMatcher {
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
m.SetSeqs(a, b)
return &m
}
// Set two sequences to be compared.
func (m *SequenceMatcher) SetSeqs(a, b []string) {
m.SetSeq1(a)
m.SetSeq2(b)
}
// Set the first sequence to be compared. The second sequence to be compared is
// not changed.
//
// SequenceMatcher computes and caches detailed information about the second
// sequence, so if you want to compare one sequence S against many sequences,
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
// sequences.
//
// See also SetSeqs() and SetSeq2().
func (m *SequenceMatcher) SetSeq1(a []string) {
if &a == &m.a {
return
}
m.a = a
m.matchingBlocks = nil
m.opCodes = nil
}
// Set the second sequence to be compared. The first sequence to be compared is
// not changed.
func (m *SequenceMatcher) SetSeq2(b []string) {
if &b == &m.b {
return
}
m.b = b
m.matchingBlocks = nil
m.opCodes = nil
m.fullBCount = nil
m.chainB()
}
func (m *SequenceMatcher) chainB() {
// Populate line -> index mapping
b2j := map[string][]int{}
for i, s := range m.b {
indices := b2j[s]
indices = append(indices, i)
b2j[s] = indices
}
// Purge junk elements
m.bJunk = map[string]struct{}{}
if m.IsJunk != nil {
junk := m.bJunk
for s, _ := range b2j {
if m.IsJunk(s) {
junk[s] = struct{}{}
}
}
for s, _ := range junk {
delete(b2j, s)
}
}
// Purge remaining popular elements
popular := map[string]struct{}{}
n := len(m.b)
if m.autoJunk && n >= 200 {
ntest := n/100 + 1
for s, indices := range b2j {
if len(indices) > ntest {
popular[s] = struct{}{}
}
}
for s, _ := range popular {
delete(b2j, s)
}
}
m.bPopular = popular
m.b2j = b2j
}
func (m *SequenceMatcher) isBJunk(s string) bool {
_, ok := m.bJunk[s]
return ok
}
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
//
// If IsJunk is not defined:
//
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
// alo <= i <= i+k <= ahi
// blo <= j <= j+k <= bhi
// and for all (i',j',k') meeting those conditions,
// k >= k'
// i <= i'
// and if i == i', j <= j'
//
// In other words, of all maximal matching blocks, return one that
// starts earliest in a, and of all those maximal matching blocks that
// start earliest in a, return the one that starts earliest in b.
//
// If IsJunk is defined, first the longest matching block is
// determined as above, but with the additional restriction that no
// junk element appears in the block. Then that block is extended as
// far as possible by matching (only) junk elements on both sides. So
// the resulting block never matches on junk except as identical junk
// happens to be adjacent to an "interesting" match.
//
// If no blocks match, return (alo, blo, 0).
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
// CAUTION: stripping common prefix or suffix would be incorrect.
// E.g.,
// ab
// acab
// Longest matching block is "ab", but if common prefix is
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
// strip, so ends up claiming that ab is changed to acab by
// inserting "ca" in the middle. That's minimal but unintuitive:
// "it's obvious" that someone inserted "ac" at the front.
// Windiff ends up at the same place as diff, but by pairing up
// the unique 'b's and then matching the first two 'a's.
besti, bestj, bestsize := alo, blo, 0
// find longest junk-free match
// during an iteration of the loop, j2len[j] = length of longest
// junk-free match ending with a[i-1] and b[j]
j2len := map[int]int{}
for i := alo; i != ahi; i++ {
// look at all instances of a[i] in b; note that because
// b2j has no junk keys, the loop is skipped if a[i] is junk
newj2len := map[int]int{}
for _, j := range m.b2j[m.a[i]] {
// a[i] matches b[j]
if j < blo {
continue
}
if j >= bhi {
break
}
k := j2len[j-1] + 1
newj2len[j] = k
if k > bestsize {
besti, bestj, bestsize = i-k+1, j-k+1, k
}
}
j2len = newj2len
}
// Extend the best by non-junk elements on each end. In particular,
// "popular" non-junk elements aren't in b2j, which greatly speeds
// the inner loop above, but also means "the best" match so far
// doesn't contain any junk *or* popular non-junk elements.
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
!m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
// Now that we have a wholly interesting match (albeit possibly
// empty!), we may as well suck up the matching junk on each
// side of it too. Can't think of a good reason not to, and it
// saves post-processing the (possibly considerable) expense of
// figuring out what to do with it. In the case of an empty
// interesting match, this is clearly the right thing to do,
// because no other kind of match is possible in the regions.
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
return Match{A: besti, B: bestj, Size: bestsize}
}
// Return list of triples describing matching subsequences.
//
// Each triple is of the form (i, j, n), and means that
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
// adjacent triples in the list, and the second is not the last triple in the
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
// adjacent equal blocks.
//
// The last triple is a dummy, (len(a), len(b), 0), and is the only
// triple with n==0.
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
if m.matchingBlocks != nil {
return m.matchingBlocks
}
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
match := m.findLongestMatch(alo, ahi, blo, bhi)
i, j, k := match.A, match.B, match.Size
if match.Size > 0 {
if alo < i && blo < j {
matched = matchBlocks(alo, i, blo, j, matched)
}
matched = append(matched, match)
if i+k < ahi && j+k < bhi {
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
}
}
return matched
}
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
// It's possible that we have adjacent equal blocks in the
// matching_blocks list now.
nonAdjacent := []Match{}
i1, j1, k1 := 0, 0, 0
for _, b := range matched {
// Is this block adjacent to i1, j1, k1?
i2, j2, k2 := b.A, b.B, b.Size
if i1+k1 == i2 && j1+k1 == j2 {
// Yes, so collapse them -- this just increases the length of
// the first block by the length of the second, and the first
// block so lengthened remains the block to compare against.
k1 += k2
} else {
// Not adjacent. Remember the first block (k1==0 means it's
// the dummy we started with), and make the second block the
// new block to compare against.
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
i1, j1, k1 = i2, j2, k2
}
}
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
m.matchingBlocks = nonAdjacent
return m.matchingBlocks
}
// Return list of 5-tuples describing how to turn a into b.
//
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
// tuple preceding it, and likewise for j1 == the previous j2.
//
// The tags are characters, with these meanings:
//
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
//
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
//
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
//
// 'e' (equal): a[i1:i2] == b[j1:j2]
func (m *SequenceMatcher) GetOpCodes() []OpCode {
if m.opCodes != nil {
return m.opCodes
}
i, j := 0, 0
matching := m.GetMatchingBlocks()
opCodes := make([]OpCode, 0, len(matching))
for _, m := range matching {
// invariant: we've pumped out correct diffs to change
// a[:i] into b[:j], and the next matching block is
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
// out a diff to change a[i:ai] into b[j:bj], pump out
// the matching block, and move (i,j) beyond the match
ai, bj, size := m.A, m.B, m.Size
tag := byte(0)
if i < ai && j < bj {
tag = 'r'
} else if i < ai {
tag = 'd'
} else if j < bj {
tag = 'i'
}
if tag > 0 {
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
}
i, j = ai+size, bj+size
// the list of matching blocks is terminated by a
// sentinel with size 0
if size > 0 {
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
}
}
m.opCodes = opCodes
return m.opCodes
}
// Isolate change clusters by eliminating ranges with no changes.
//
// Return a generator of groups with up to n lines of context.
// Each group is in the same format as returned by GetOpCodes().
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
if n < 0 {
n = 3
}
codes := m.GetOpCodes()
if len(codes) == 0 {
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
}
// Fixup leading and trailing groups if they show no changes.
if codes[0].Tag == 'e' {
c := codes[0]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
}
if codes[len(codes)-1].Tag == 'e' {
c := codes[len(codes)-1]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
}
nn := n + n
groups := [][]OpCode{}
group := []OpCode{}
for _, c := range codes {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
// End the current group and start a new one whenever
// there is a large range with no changes.
if c.Tag == 'e' && i2-i1 > nn {
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
j1, min(j2, j1+n)})
groups = append(groups, group)
group = []OpCode{}
i1, j1 = max(i1, i2-n), max(j1, j2-n)
}
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
}
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
groups = append(groups, group)
}
return groups
}
// Return a measure of the sequences' similarity (float in [0,1]).
//
// Where T is the total number of elements in both sequences, and
// M is the number of matches, this is 2.0*M / T.
// Note that this is 1 if the sequences are identical, and 0 if
// they have nothing in common.
//
// .Ratio() is expensive to compute if you haven't already computed
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
// want to try .QuickRatio() or .RealQuickRation() first to get an
// upper bound.
func (m *SequenceMatcher) Ratio() float64 {
matches := 0
for _, m := range m.GetMatchingBlocks() {
matches += m.Size
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() relatively quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute.
func (m *SequenceMatcher) QuickRatio() float64 {
// viewing a and b as multisets, set matches to the cardinality
// of their intersection; this counts the number of matches
// without regard to order, so is clearly an upper bound
if m.fullBCount == nil {
m.fullBCount = map[string]int{}
for _, s := range m.b {
m.fullBCount[s] = m.fullBCount[s] + 1
}
}
// avail[x] is the number of times x appears in 'b' less the
// number of times we've seen it in 'a' so far ... kinda
avail := map[string]int{}
matches := 0
for _, s := range m.a {
n, ok := avail[s]
if !ok {
n = m.fullBCount[s]
}
avail[s] = n - 1
if n > 0 {
matches += 1
}
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() very quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute than either .Ratio() or .QuickRatio().
func (m *SequenceMatcher) RealQuickRatio() float64 {
la, lb := len(m.a), len(m.b)
return calculateRatio(min(la, lb), la+lb)
}
// Convert range to the "ed" format
func formatRangeUnified(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 1 {
return fmt.Sprintf("%d", beginning)
}
if length == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
return fmt.Sprintf("%d,%d", beginning, length)
}
// Unified diff parameters
type UnifiedDiff struct {
A []string // First sequence lines
FromFile string // First file name
FromDate string // First file time
B []string // Second sequence lines
ToFile string // Second file name
ToDate string // Second file time
Eol string // Headers end of line, defaults to LF
Context int // Number of context lines
}
// Compare two sequences of lines; generate the delta as a unified diff.
//
// Unified diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by 'n' which
// defaults to three.
//
// By default, the diff control lines (those with ---, +++, or @@) are
// created with a trailing newline. This is helpful so that inputs
// created from file.readlines() result in diffs that are suitable for
// file.writelines() since both the inputs and outputs have trailing
// newlines.
//
// For inputs that do not have trailing newlines, set the lineterm
// argument to "" so that the output will be uniformly newline free.
//
// The unidiff format normally has a header for filenames and modification
// times. Any or all of these may be specified using strings for
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
// The modification times are normally expressed in the ISO 8601 format.
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
w := func(format string, args ...interface{}) error {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
return err
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
err := w("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
if err != nil {
return err
}
err = w("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
if err != nil {
return err
}
}
first, last := g[0], g[len(g)-1]
range1 := formatRangeUnified(first.I1, last.I2)
range2 := formatRangeUnified(first.J1, last.J2)
if err := w("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
return err
}
for _, c := range g {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
if c.Tag == 'e' {
for _, line := range diff.A[i1:i2] {
if err := w(" " + line); err != nil {
return err
}
}
continue
}
if c.Tag == 'r' || c.Tag == 'd' {
for _, line := range diff.A[i1:i2] {
if err := w("-" + line); err != nil {
return err
}
}
}
if c.Tag == 'r' || c.Tag == 'i' {
for _, line := range diff.B[j1:j2] {
if err := w("+" + line); err != nil {
return err
}
}
}
}
}
return nil
}
// Like WriteUnifiedDiff but returns the diff a string.
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteUnifiedDiff(w, diff)
return string(w.Bytes()), err
}
// Convert range to the "ed" format.
func formatRangeContext(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
if length <= 1 {
return fmt.Sprintf("%d", beginning)
}
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
}
type ContextDiff UnifiedDiff
// Compare two sequences of lines; generate the delta as a context diff.
//
// Context diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by diff.Context
// which defaults to three.
//
// By default, the diff control lines (those with *** or ---) are
// created with a trailing newline.
//
// For inputs that do not have trailing newlines, set the diff.Eol
// argument to "" so that the output will be uniformly newline free.
//
// The context diff format normally has a header for filenames and
// modification times. Any or all of these may be specified using
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
// The modification times are normally expressed in the ISO 8601 format.
// If not specified, the strings default to blanks.
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
var diffErr error
w := func(format string, args ...interface{}) {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
if diffErr == nil && err != nil {
diffErr = err
}
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
prefix := map[byte]string{
'i': "+ ",
'd': "- ",
'r': "! ",
'e': " ",
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
w("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
w("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
}
first, last := g[0], g[len(g)-1]
w("***************" + diff.Eol)
range1 := formatRangeContext(first.I1, last.I2)
w("*** %s ****%s", range1, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'd' {
for _, cc := range g {
if cc.Tag == 'i' {
continue
}
for _, line := range diff.A[cc.I1:cc.I2] {
w(prefix[cc.Tag] + line)
}
}
break
}
}
range2 := formatRangeContext(first.J1, last.J2)
w("--- %s ----%s", range2, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'i' {
for _, cc := range g {
if cc.Tag == 'd' {
continue
}
for _, line := range diff.B[cc.J1:cc.J2] {
w(prefix[cc.Tag] + line)
}
}
break
}
}
}
return diffErr
}
// Like WriteContextDiff but returns the diff a string.
func GetContextDiffString(diff ContextDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteContextDiff(w, diff)
return string(w.Bytes()), err
}
// Split a string on "\n" while preserving them. The output can be used
// as input for UnifiedDiff and ContextDiff structures.
func SplitLines(s string) []string {
lines := strings.SplitAfter(s, "\n")
lines[len(lines)-1] += "\n"
return lines
}

@ -1,352 +0,0 @@
package difflib
import (
"bytes"
"fmt"
"math"
"reflect"
"strings"
"testing"
)
func assertAlmostEqual(t *testing.T, a, b float64, places int) {
if math.Abs(a-b) > math.Pow10(-places) {
t.Errorf("%.7f != %.7f", a, b)
}
}
func assertEqual(t *testing.T, a, b interface{}) {
if !reflect.DeepEqual(a, b) {
t.Errorf("%v != %v", a, b)
}
}
func splitChars(s string) []string {
chars := make([]string, 0, len(s))
// Assume ASCII inputs
for i := 0; i != len(s); i++ {
chars = append(chars, string(s[i]))
}
return chars
}
func TestSequenceMatcherRatio(t *testing.T) {
s := NewMatcher(splitChars("abcd"), splitChars("bcde"))
assertEqual(t, s.Ratio(), 0.75)
assertEqual(t, s.QuickRatio(), 0.75)
assertEqual(t, s.RealQuickRatio(), 1.0)
}
func TestGetOptCodes(t *testing.T) {
a := "qabxcd"
b := "abycdf"
s := NewMatcher(splitChars(a), splitChars(b))
w := &bytes.Buffer{}
for _, op := range s.GetOpCodes() {
fmt.Fprintf(w, "%s a[%d:%d], (%s) b[%d:%d] (%s)\n", string(op.Tag),
op.I1, op.I2, a[op.I1:op.I2], op.J1, op.J2, b[op.J1:op.J2])
}
result := string(w.Bytes())
expected := `d a[0:1], (q) b[0:0] ()
e a[1:3], (ab) b[0:2] (ab)
r a[3:4], (x) b[2:3] (y)
e a[4:6], (cd) b[3:5] (cd)
i a[6:6], () b[5:6] (f)
`
if expected != result {
t.Errorf("unexpected op codes: \n%s", result)
}
}
func TestGroupedOpCodes(t *testing.T) {
a := []string{}
for i := 0; i != 39; i++ {
a = append(a, fmt.Sprintf("%02d", i))
}
b := []string{}
b = append(b, a[:8]...)
b = append(b, " i")
b = append(b, a[8:19]...)
b = append(b, " x")
b = append(b, a[20:22]...)
b = append(b, a[27:34]...)
b = append(b, " y")
b = append(b, a[35:]...)
s := NewMatcher(a, b)
w := &bytes.Buffer{}
for _, g := range s.GetGroupedOpCodes(-1) {
fmt.Fprintf(w, "group\n")
for _, op := range g {
fmt.Fprintf(w, " %s, %d, %d, %d, %d\n", string(op.Tag),
op.I1, op.I2, op.J1, op.J2)
}
}
result := string(w.Bytes())
expected := `group
e, 5, 8, 5, 8
i, 8, 8, 8, 9
e, 8, 11, 9, 12
group
e, 16, 19, 17, 20
r, 19, 20, 20, 21
e, 20, 22, 21, 23
d, 22, 27, 23, 23
e, 27, 30, 23, 26
group
e, 31, 34, 27, 30
r, 34, 35, 30, 31
e, 35, 38, 31, 34
`
if expected != result {
t.Errorf("unexpected op codes: \n%s", result)
}
}
func ExampleGetUnifiedDiffString() {
a := `one
two
three
four`
b := `zero
one
three
four`
diff := UnifiedDiff{
A: SplitLines(a),
B: SplitLines(b),
FromFile: "Original",
FromDate: "2005-01-26 23:30:50",
ToFile: "Current",
ToDate: "2010-04-02 10:20:52",
Context: 3,
}
result, _ := GetUnifiedDiffString(diff)
fmt.Printf(strings.Replace(result, "\t", " ", -1))
// Output:
// --- Original 2005-01-26 23:30:50
// +++ Current 2010-04-02 10:20:52
// @@ -1,4 +1,4 @@
// +zero
// one
// -two
// three
// four
}
func ExampleGetContextDiffString() {
a := `one
two
three
four`
b := `zero
one
tree
four`
diff := ContextDiff{
A: SplitLines(a),
B: SplitLines(b),
FromFile: "Original",
ToFile: "Current",
Context: 3,
Eol: "\n",
}
result, _ := GetContextDiffString(diff)
fmt.Printf(strings.Replace(result, "\t", " ", -1))
// Output:
// *** Original
// --- Current
// ***************
// *** 1,4 ****
// one
// ! two
// ! three
// four
// --- 1,4 ----
// + zero
// one
// ! tree
// four
}
func rep(s string, count int) string {
return strings.Repeat(s, count)
}
func TestWithAsciiOneInsert(t *testing.T) {
sm := NewMatcher(splitChars(rep("b", 100)),
splitChars("a"+rep("b", 100)))
assertAlmostEqual(t, sm.Ratio(), 0.995, 3)
assertEqual(t, sm.GetOpCodes(),
[]OpCode{{'i', 0, 0, 0, 1}, {'e', 0, 100, 1, 101}})
assertEqual(t, len(sm.bPopular), 0)
sm = NewMatcher(splitChars(rep("b", 100)),
splitChars(rep("b", 50)+"a"+rep("b", 50)))
assertAlmostEqual(t, sm.Ratio(), 0.995, 3)
assertEqual(t, sm.GetOpCodes(),
[]OpCode{{'e', 0, 50, 0, 50}, {'i', 50, 50, 50, 51}, {'e', 50, 100, 51, 101}})
assertEqual(t, len(sm.bPopular), 0)
}
func TestWithAsciiOnDelete(t *testing.T) {
sm := NewMatcher(splitChars(rep("a", 40)+"c"+rep("b", 40)),
splitChars(rep("a", 40)+rep("b", 40)))
assertAlmostEqual(t, sm.Ratio(), 0.994, 3)
assertEqual(t, sm.GetOpCodes(),
[]OpCode{{'e', 0, 40, 0, 40}, {'d', 40, 41, 40, 40}, {'e', 41, 81, 40, 80}})
}
func TestWithAsciiBJunk(t *testing.T) {
isJunk := func(s string) bool {
return s == " "
}
sm := NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
splitChars(rep("a", 44)+rep("b", 40)), true, isJunk)
assertEqual(t, sm.bJunk, map[string]struct{}{})
sm = NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
splitChars(rep("a", 44)+rep("b", 40)+rep(" ", 20)), false, isJunk)
assertEqual(t, sm.bJunk, map[string]struct{}{" ": struct{}{}})
isJunk = func(s string) bool {
return s == " " || s == "b"
}
sm = NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
splitChars(rep("a", 44)+rep("b", 40)+rep(" ", 20)), false, isJunk)
assertEqual(t, sm.bJunk, map[string]struct{}{" ": struct{}{}, "b": struct{}{}})
}
func TestSFBugsRatioForNullSeqn(t *testing.T) {
sm := NewMatcher(nil, nil)
assertEqual(t, sm.Ratio(), 1.0)
assertEqual(t, sm.QuickRatio(), 1.0)
assertEqual(t, sm.RealQuickRatio(), 1.0)
}
func TestSFBugsComparingEmptyLists(t *testing.T) {
groups := NewMatcher(nil, nil).GetGroupedOpCodes(-1)
assertEqual(t, len(groups), 0)
diff := UnifiedDiff{
FromFile: "Original",
ToFile: "Current",
Context: 3,
}
result, err := GetUnifiedDiffString(diff)
assertEqual(t, err, nil)
assertEqual(t, result, "")
}
func TestOutputFormatRangeFormatUnified(t *testing.T) {
// Per the diff spec at http://www.unix.org/single_unix_specification/
//
// Each <range> field shall be of the form:
// %1d", <beginning line number> if the range contains exactly one line,
// and:
// "%1d,%1d", <beginning line number>, <number of lines> otherwise.
// If a range is empty, its beginning line number shall be the number of
// the line just before the range, or 0 if the empty range starts the file.
fm := formatRangeUnified
assertEqual(t, fm(3, 3), "3,0")
assertEqual(t, fm(3, 4), "4")
assertEqual(t, fm(3, 5), "4,2")
assertEqual(t, fm(3, 6), "4,3")
assertEqual(t, fm(0, 0), "0,0")
}
func TestOutputFormatRangeFormatContext(t *testing.T) {
// Per the diff spec at http://www.unix.org/single_unix_specification/
//
// The range of lines in file1 shall be written in the following format
// if the range contains two or more lines:
// "*** %d,%d ****\n", <beginning line number>, <ending line number>
// and the following format otherwise:
// "*** %d ****\n", <ending line number>
// The ending line number of an empty range shall be the number of the preceding line,
// or 0 if the range is at the start of the file.
//
// Next, the range of lines in file2 shall be written in the following format
// if the range contains two or more lines:
// "--- %d,%d ----\n", <beginning line number>, <ending line number>
// and the following format otherwise:
// "--- %d ----\n", <ending line number>
fm := formatRangeContext
assertEqual(t, fm(3, 3), "3")
assertEqual(t, fm(3, 4), "4")
assertEqual(t, fm(3, 5), "4,5")
assertEqual(t, fm(3, 6), "4,6")
assertEqual(t, fm(0, 0), "0")
}
func TestOutputFormatTabDelimiter(t *testing.T) {
diff := UnifiedDiff{
A: splitChars("one"),
B: splitChars("two"),
FromFile: "Original",
FromDate: "2005-01-26 23:30:50",
ToFile: "Current",
ToDate: "2010-04-12 10:20:52",
Eol: "\n",
}
ud, err := GetUnifiedDiffString(diff)
assertEqual(t, err, nil)
assertEqual(t, SplitLines(ud)[:2], []string{
"--- Original\t2005-01-26 23:30:50\n",
"+++ Current\t2010-04-12 10:20:52\n",
})
cd, err := GetContextDiffString(ContextDiff(diff))
assertEqual(t, err, nil)
assertEqual(t, SplitLines(cd)[:2], []string{
"*** Original\t2005-01-26 23:30:50\n",
"--- Current\t2010-04-12 10:20:52\n",
})
}
func TestOutputFormatNoTrailingTabOnEmptyFiledate(t *testing.T) {
diff := UnifiedDiff{
A: splitChars("one"),
B: splitChars("two"),
FromFile: "Original",
ToFile: "Current",
Eol: "\n",
}
ud, err := GetUnifiedDiffString(diff)
assertEqual(t, err, nil)
assertEqual(t, SplitLines(ud)[:2], []string{"--- Original\n", "+++ Current\n"})
cd, err := GetContextDiffString(ContextDiff(diff))
assertEqual(t, err, nil)
assertEqual(t, SplitLines(cd)[:2], []string{"*** Original\n", "--- Current\n"})
}
func TestSplitLines(t *testing.T) {
allTests := []struct {
input string
want []string
}{
{"foo", []string{"foo\n"}},
{"foo\nbar", []string{"foo\n", "bar\n"}},
{"foo\nbar\n", []string{"foo\n", "bar\n", "\n"}},
}
for _, test := range allTests {
assertEqual(t, SplitLines(test.input), test.want)
}
}
func benchmarkSplitLines(b *testing.B, count int) {
str := strings.Repeat("foo\n", count)
b.ResetTimer()
n := 0
for i := 0; i < b.N; i++ {
n += len(SplitLines(str))
}
}
func BenchmarkSplitLines100(b *testing.B) {
benchmarkSplitLines(b, 100)
}
func BenchmarkSplitLines10000(b *testing.B) {
benchmarkSplitLines(b, 10000)
}

@ -1,24 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
.DS_Store

@ -1,15 +0,0 @@
language: go
sudo: false
go:
- 1.1
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- tip
script:
- go test -v ./...

@ -1,21 +0,0 @@
{
"ImportPath": "github.com/stretchr/testify",
"GoVersion": "go1.5",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
},
{
"ImportPath": "github.com/pmezard/go-difflib/difflib",
"Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
},
{
"ImportPath": "github.com/stretchr/objx",
"Rev": "cbeaeb16a013161a98496fad62933b1d21786672"
}
]
}

@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

@ -1,22 +0,0 @@
Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell
Please consider promoting this project if you find it useful.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -1,22 +0,0 @@
Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell
Please consider promoting this project if you find it useful.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -1,332 +0,0 @@
Testify - Thou Shalt Write Tests
================================
[![Build Status](https://travis-ci.org/stretchr/testify.svg)](https://travis-ci.org/stretchr/testify)
Go code (golang) set of packages that provide many tools for testifying that your code will behave as you intend.
Features include:
* [Easy assertions](#assert-package)
* [Mocking](#mock-package)
* [HTTP response trapping](#http-package)
* [Testing suite interfaces and functions](#suite-package)
Get started:
* Install testify with [one line of code](#installation), or [update it with another](#staying-up-to-date)
* For an introduction to writing test code in Go, see http://golang.org/doc/code.html#Testing
* Check out the API Documentation http://godoc.org/github.com/stretchr/testify
* To make your testing life easier, check out our other project, [gorc](http://github.com/stretchr/gorc)
* A little about [Test-Driven Development (TDD)](http://en.wikipedia.org/wiki/Test-driven_development)
[`assert`](http://godoc.org/github.com/stretchr/testify/assert "API documentation") package
-------------------------------------------------------------------------------------------
The `assert` package provides some helpful methods that allow you to write better test code in Go.
* Prints friendly, easy to read failure descriptions
* Allows for very readable code
* Optionally annotate each assertion with a message
See it in action:
```go
package yours
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
// assert equality
assert.Equal(t, 123, 123, "they should be equal")
// assert inequality
assert.NotEqual(t, 123, 456, "they should not be equal")
// assert for nil (good for errors)
assert.Nil(t, object)
// assert for not nil (good when you expect something)
if assert.NotNil(t, object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal(t, "Something", object.Value)
}
}
```
* Every assert func takes the `testing.T` object as the first argument. This is how it writes the errors out through the normal `go test` capabilities.
* Every assert func returns a bool indicating whether the assertion was successful or not, this is useful for if you want to go on making further assertions under certain conditions.
if you assert many times, use the below:
```go
package yours
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
assert := assert.New(t)
// assert equality
assert.Equal(123, 123, "they should be equal")
// assert inequality
assert.NotEqual(123, 456, "they should not be equal")
// assert for nil (good for errors)
assert.Nil(object)
// assert for not nil (good when you expect something)
if assert.NotNil(object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal("Something", object.Value)
}
}
```
[`require`](http://godoc.org/github.com/stretchr/testify/require "API documentation") package
---------------------------------------------------------------------------------------------
The `require` package provides same global functions as the `assert` package, but instead of returning a boolean result they terminate current test.
See [t.FailNow](http://golang.org/pkg/testing/#T.FailNow) for details.
[`http`](http://godoc.org/github.com/stretchr/testify/http "API documentation") package
---------------------------------------------------------------------------------------
The `http` package contains test objects useful for testing code that relies on the `net/http` package. Check out the [(deprecated) API documentation for the `http` package](http://godoc.org/github.com/stretchr/testify/http).
We recommend you use [httptest](http://golang.org/pkg/net/http/httptest) instead.
[`mock`](http://godoc.org/github.com/stretchr/testify/mock "API documentation") package
----------------------------------------------------------------------------------------
The `mock` package provides a mechanism for easily writing mock objects that can be used in place of real objects when writing test code.
An example test function that tests a piece of code that relies on an external object `testObj`, can setup expectations (testify) and assert that they indeed happened:
```go
package yours
import (
"testing"
"github.com/stretchr/testify/mock"
)
/*
Test objects
*/
// MyMockedObject is a mocked object that implements an interface
// that describes an object that the code I am testing relies on.
type MyMockedObject struct{
mock.Mock
}
// DoSomething is a method on MyMockedObject that implements some interface
// and just records the activity, and returns what the Mock object tells it to.
//
// In the real object, this method would do something useful, but since this
// is a mocked object - we're just going to stub it out.
//
// NOTE: This method is not being tested here, code that uses this object is.
func (m *MyMockedObject) DoSomething(number int) (bool, error) {
args := m.Called(number)
return args.Bool(0), args.Error(1)
}
/*
Actual test functions
*/
// TestSomething is an example of how to use our test object to
// make assertions about some target code we are testing.
func TestSomething(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)
// setup expectations
testObj.On("DoSomething", 123).Return(true, nil)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
}
```
For more information on how to write mock code, check out the [API documentation for the `mock` package](http://godoc.org/github.com/stretchr/testify/mock).
You can use the [mockery tool](http://github.com/vektra/mockery) to autogenerate the mock code against an interface as well, making using mocks much quicker.
[`suite`](http://godoc.org/github.com/stretchr/testify/suite "API documentation") package
-----------------------------------------------------------------------------------------
The `suite` package provides functionality that you might be used to from more common object oriented languages. With it, you can build a testing suite as a struct, build setup/teardown methods and testing methods on your struct, and run them with 'go test' as per normal.
An example suite is shown below:
```go
// Basic imports
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
// Define the suite, and absorb the built-in basic suite
// functionality from testify - including a T() method which
// returns the current testing context
type ExampleTestSuite struct {
suite.Suite
VariableThatShouldStartAtFive int
}
// Make sure that VariableThatShouldStartAtFive is set to five
// before each test
func (suite *ExampleTestSuite) SetupTest() {
suite.VariableThatShouldStartAtFive = 5
}
// All methods that begin with "Test" are run as tests within a
// suite.
func (suite *ExampleTestSuite) TestExample() {
assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
}
// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestExampleTestSuite(t *testing.T) {
suite.Run(t, new(ExampleTestSuite))
}
```
For a more complete example, using all of the functionality provided by the suite package, look at our [example testing suite](https://github.com/stretchr/testify/blob/master/suite/suite_test.go)
For more information on writing suites, check out the [API documentation for the `suite` package](http://godoc.org/github.com/stretchr/testify/suite).
`Suite` object has assertion methods:
```go
// Basic imports
import (
"testing"
"github.com/stretchr/testify/suite"
)
// Define the suite, and absorb the built-in basic suite
// functionality from testify - including assertion methods.
type ExampleTestSuite struct {
suite.Suite
VariableThatShouldStartAtFive int
}
// Make sure that VariableThatShouldStartAtFive is set to five
// before each test
func (suite *ExampleTestSuite) SetupTest() {
suite.VariableThatShouldStartAtFive = 5
}
// All methods that begin with "Test" are run as tests within a
// suite.
func (suite *ExampleTestSuite) TestExample() {
suite.Equal(suite.VariableThatShouldStartAtFive, 5)
}
// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestExampleTestSuite(t *testing.T) {
suite.Run(t, new(ExampleTestSuite))
}
```
------
Installation
============
To install Testify, use `go get`:
* Latest version: go get github.com/stretchr/testify
* Specific version: go get gopkg.in/stretchr/testify.v1
This will then make the following packages available to you:
github.com/stretchr/testify/assert
github.com/stretchr/testify/mock
github.com/stretchr/testify/http
Import the `testify/assert` package into your code using this template:
```go
package yours
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
assert.True(t, true, "True is true!")
}
```
------
Staying up to date
==================
To update Testify to the latest version, use `go get -u github.com/stretchr/testify`.
------
Version History
===============
* 1.0 - New package versioning strategy adopted.
------
Contributing
============
Please feel free to submit issues, fork the repository and send pull requests!
When submitting an issue, we ask that you please include a complete test function that demonstrates the issue. Extra credit for those using Testify to write the test code that demonstrates it.
------
Licence
=======
Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell
Please consider promoting this project if you find it useful.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -1,287 +0,0 @@
// This program reads all assertion functions from the assert package and
// automatically generates the corersponding requires and forwarded assertions
package main
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/build"
"go/doc"
"go/importer"
"go/parser"
"go/token"
"go/types"
"io"
"io/ioutil"
"log"
"os"
"path"
"strings"
"text/template"
"github.com/ernesto-jimenez/gogen/imports"
)
var (
pkg = flag.String("assert-path", "github.com/stretchr/testify/assert", "Path to the assert package")
outputPkg = flag.String("output-package", "", "package for the resulting code")
tmplFile = flag.String("template", "", "What file to load the function template from")
out = flag.String("out", "", "What file to write the source code to")
)
func main() {
flag.Parse()
scope, docs, err := parsePackageSource(*pkg)
if err != nil {
log.Fatal(err)
}
importer, funcs, err := analyzeCode(scope, docs)
if err != nil {
log.Fatal(err)
}
if err := generateCode(importer, funcs); err != nil {
log.Fatal(err)
}
}
func generateCode(importer imports.Importer, funcs []testFunc) error {
buff := bytes.NewBuffer(nil)
tmplHead, tmplFunc, err := parseTemplates()
if err != nil {
return err
}
// Generate header
if err := tmplHead.Execute(buff, struct {
Name string
Imports map[string]string
}{
*outputPkg,
importer.Imports(),
}); err != nil {
return err
}
// Generate funcs
for _, fn := range funcs {
buff.Write([]byte("\n\n"))
if err := tmplFunc.Execute(buff, &fn); err != nil {
return err
}
}
// Write file
output, err := outputFile()
if err != nil {
return err
}
defer output.Close()
_, err = io.Copy(output, buff)
return err
}
func parseTemplates() (*template.Template, *template.Template, error) {
tmplHead, err := template.New("header").Parse(headerTemplate)
if err != nil {
return nil, nil, err
}
if *tmplFile != "" {
f, err := ioutil.ReadFile(*tmplFile)
if err != nil {
return nil, nil, err
}
funcTemplate = string(f)
}
tmpl, err := template.New("function").Parse(funcTemplate)
if err != nil {
return nil, nil, err
}
return tmplHead, tmpl, nil
}
func outputFile() (*os.File, error) {
filename := *out
if filename == "-" || (filename == "" && *tmplFile == "") {
return os.Stdout, nil
}
if filename == "" {
filename = strings.TrimSuffix(strings.TrimSuffix(*tmplFile, ".tmpl"), ".go") + ".go"
}
return os.Create(filename)
}
// analyzeCode takes the types scope and the docs and returns the import
// information and information about all the assertion functions.
func analyzeCode(scope *types.Scope, docs *doc.Package) (imports.Importer, []testFunc, error) {
testingT := scope.Lookup("TestingT").Type().Underlying().(*types.Interface)
importer := imports.New(*outputPkg)
var funcs []testFunc
// Go through all the top level functions
for _, fdocs := range docs.Funcs {
// Find the function
obj := scope.Lookup(fdocs.Name)
fn, ok := obj.(*types.Func)
if !ok {
continue
}
// Check function signatuer has at least two arguments
sig := fn.Type().(*types.Signature)
if sig.Params().Len() < 2 {
continue
}
// Check first argument is of type testingT
first, ok := sig.Params().At(0).Type().(*types.Named)
if !ok {
continue
}
firstType, ok := first.Underlying().(*types.Interface)
if !ok {
continue
}
if !types.Implements(firstType, testingT) {
continue
}
funcs = append(funcs, testFunc{*outputPkg, fdocs, fn})
importer.AddImportsFrom(sig.Params())
}
return importer, funcs, nil
}
// parsePackageSource returns the types scope and the package documentation from the pa
func parsePackageSource(pkg string) (*types.Scope, *doc.Package, error) {
pd, err := build.Import(pkg, ".", 0)
if err != nil {
return nil, nil, err
}
fset := token.NewFileSet()
files := make(map[string]*ast.File)
fileList := make([]*ast.File, len(pd.GoFiles))
for i, fname := range pd.GoFiles {
src, err := ioutil.ReadFile(path.Join(pd.SrcRoot, pd.ImportPath, fname))
if err != nil {
return nil, nil, err
}
f, err := parser.ParseFile(fset, fname, src, parser.ParseComments|parser.AllErrors)
if err != nil {
return nil, nil, err
}
files[fname] = f
fileList[i] = f
}
cfg := types.Config{
Importer: importer.Default(),
}
info := types.Info{
Defs: make(map[*ast.Ident]types.Object),
}
tp, err := cfg.Check(pkg, fset, fileList, &info)
if err != nil {
return nil, nil, err
}
scope := tp.Scope()
ap, _ := ast.NewPackage(fset, files, nil, nil)
docs := doc.New(ap, pkg, 0)
return scope, docs, nil
}
type testFunc struct {
CurrentPkg string
DocInfo *doc.Func
TypeInfo *types.Func
}
func (f *testFunc) Qualifier(p *types.Package) string {
if p == nil || p.Name() == f.CurrentPkg {
return ""
}
return p.Name()
}
func (f *testFunc) Params() string {
sig := f.TypeInfo.Type().(*types.Signature)
params := sig.Params()
p := ""
comma := ""
to := params.Len()
var i int
if sig.Variadic() {
to--
}
for i = 1; i < to; i++ {
param := params.At(i)
p += fmt.Sprintf("%s%s %s", comma, param.Name(), types.TypeString(param.Type(), f.Qualifier))
comma = ", "
}
if sig.Variadic() {
param := params.At(params.Len() - 1)
p += fmt.Sprintf("%s%s ...%s", comma, param.Name(), types.TypeString(param.Type().(*types.Slice).Elem(), f.Qualifier))
}
return p
}
func (f *testFunc) ForwardedParams() string {
sig := f.TypeInfo.Type().(*types.Signature)
params := sig.Params()
p := ""
comma := ""
to := params.Len()
var i int
if sig.Variadic() {
to--
}
for i = 1; i < to; i++ {
param := params.At(i)
p += fmt.Sprintf("%s%s", comma, param.Name())
comma = ", "
}
if sig.Variadic() {
param := params.At(params.Len() - 1)
p += fmt.Sprintf("%s%s...", comma, param.Name())
}
return p
}
func (f *testFunc) Comment() string {
return "// " + strings.Replace(strings.TrimSpace(f.DocInfo.Doc), "\n", "\n// ", -1)
}
func (f *testFunc) CommentWithoutT(receiver string) string {
search := fmt.Sprintf("assert.%s(t, ", f.DocInfo.Name)
replace := fmt.Sprintf("%s.%s(", receiver, f.DocInfo.Name)
return strings.Replace(f.Comment(), search, replace, -1)
}
var headerTemplate = `/*
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
package {{.Name}}
import (
{{range $path, $name := .Imports}}
{{$name}} "{{$path}}"{{end}}
)
`
var funcTemplate = `{{.Comment}}
func (fwd *AssertionsForwarder) {{.DocInfo.Name}}({{.Params}}) bool {
return assert.{{.DocInfo.Name}}({{.ForwardedParams}})
}`

@ -1,387 +0,0 @@
/*
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
package assert
import (
http "net/http"
url "net/url"
time "time"
)
// Condition uses a Comparison to assert a complex condition.
func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool {
return Condition(a.t, comp, msgAndArgs...)
}
// Contains asserts that the specified string, list(array, slice...) or map contains the
// specified substring or element.
//
// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'")
// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'")
// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
return Contains(a.t, s, contains, msgAndArgs...)
}
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
// a.Empty(obj)
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
return Empty(a.t, object, msgAndArgs...)
}
// Equal asserts that two objects are equal.
//
// a.Equal(123, 123, "123 and 123 should be equal")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
return Equal(a.t, expected, actual, msgAndArgs...)
}
// EqualError asserts that a function returned an error (i.e. not `nil`)
// and that it is equal to the provided error.
//
// actualObj, err := SomeFunction()
// if assert.Error(t, err, "An error was expected") {
// assert.Equal(t, err, expectedError)
// }
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool {
return EqualError(a.t, theError, errString, msgAndArgs...)
}
// EqualValues asserts that two objects are equal or convertable to the same types
// and equal.
//
// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
return EqualValues(a.t, expected, actual, msgAndArgs...)
}
// Error asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
// if a.Error(err, "An error was expected") {
// assert.Equal(t, err, expectedError)
// }
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
return Error(a.t, err, msgAndArgs...)
}
// Exactly asserts that two objects are equal is value and type.
//
// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
return Exactly(a.t, expected, actual, msgAndArgs...)
}
// Fail reports a failure through
func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool {
return Fail(a.t, failureMessage, msgAndArgs...)
}
// FailNow fails test
func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) bool {
return FailNow(a.t, failureMessage, msgAndArgs...)
}
// False asserts that the specified value is false.
//
// a.False(myBool, "myBool should be false")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool {
return False(a.t, value, msgAndArgs...)
}
// HTTPBodyContains asserts that a specified handler returns a
// body that contains a string.
//
// a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool {
return HTTPBodyContains(a.t, handler, method, url, values, str)
}
// HTTPBodyNotContains asserts that a specified handler returns a
// body that does not contain a string.
//
// a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool {
return HTTPBodyNotContains(a.t, handler, method, url, values, str)
}
// HTTPError asserts that a specified handler returns an error status code.
//
// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) bool {
return HTTPError(a.t, handler, method, url, values)
}
// HTTPRedirect asserts that a specified handler returns a redirect status code.
//
// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) bool {
return HTTPRedirect(a.t, handler, method, url, values)
}
// HTTPSuccess asserts that a specified handler returns a success status code.
//
// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil)
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) bool {
return HTTPSuccess(a.t, handler, method, url, values)
}
// Implements asserts that an object is implemented by the specified interface.
//
// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject")
func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
return Implements(a.t, interfaceObject, object, msgAndArgs...)
}
// InDelta asserts that the two numerals are within delta of each other.
//
// a.InDelta(math.Pi, (22 / 7.0), 0.01)
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
return InDelta(a.t, expected, actual, delta, msgAndArgs...)
}
// InDeltaSlice is the same as InDelta, except it compares two slices.
func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...)
}
// InEpsilon asserts that expected and actual have a relative error less than epsilon
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...)
}
// InEpsilonSlice is the same as InEpsilon, except it compares two slices.
func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
return InEpsilonSlice(a.t, expected, actual, delta, msgAndArgs...)
}
// IsType asserts that the specified objects are of the same type.
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
return IsType(a.t, expectedType, object, msgAndArgs...)
}
// JSONEq asserts that two JSON strings are equivalent.
//
// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool {
return JSONEq(a.t, expected, actual, msgAndArgs...)
}
// Len asserts that the specified object has specific length.
// Len also fails if the object has a type that len() not accept.
//
// a.Len(mySlice, 3, "The size of slice is not 3")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool {
return Len(a.t, object, length, msgAndArgs...)
}
// Nil asserts that the specified object is nil.
//
// a.Nil(err, "err should be nothing")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool {
return Nil(a.t, object, msgAndArgs...)
}
// NoError asserts that a function returned no error (i.e. `nil`).
//
// actualObj, err := SomeFunction()
// if a.NoError(err) {
// assert.Equal(t, actualObj, expectedObj)
// }
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool {
return NoError(a.t, err, msgAndArgs...)
}
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
// specified substring or element.
//
// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'")
// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'")
// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
return NotContains(a.t, s, contains, msgAndArgs...)
}
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
// if a.NotEmpty(obj) {
// assert.Equal(t, "two", obj[1])
// }
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool {
return NotEmpty(a.t, object, msgAndArgs...)
}
// NotEqual asserts that the specified values are NOT equal.
//
// a.NotEqual(obj1, obj2, "two objects shouldn't be equal")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
return NotEqual(a.t, expected, actual, msgAndArgs...)
}
// NotNil asserts that the specified object is not nil.
//
// a.NotNil(err, "err should be something")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool {
return NotNil(a.t, object, msgAndArgs...)
}
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
//
// a.NotPanics(func(){
// RemainCalm()
// }, "Calling RemainCalm() should NOT panic")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
return NotPanics(a.t, f, msgAndArgs...)
}
// NotRegexp asserts that a specified regexp does not match a string.
//
// a.NotRegexp(regexp.MustCompile("starts"), "it's starting")
// a.NotRegexp("^start", "it's not starting")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
return NotRegexp(a.t, rx, str, msgAndArgs...)
}
// NotZero asserts that i is not the zero value for its type and returns the truth.
func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool {
return NotZero(a.t, i, msgAndArgs...)
}
// Panics asserts that the code inside the specified PanicTestFunc panics.
//
// a.Panics(func(){
// GoCrazy()
// }, "Calling GoCrazy() should panic")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
return Panics(a.t, f, msgAndArgs...)
}
// Regexp asserts that a specified regexp matches a string.
//
// a.Regexp(regexp.MustCompile("start"), "it's starting")
// a.Regexp("start...$", "it's not starting")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
return Regexp(a.t, rx, str, msgAndArgs...)
}
// True asserts that the specified value is true.
//
// a.True(myBool, "myBool should be true")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool {
return True(a.t, value, msgAndArgs...)
}
// WithinDuration asserts that the two times are within duration delta of each other.
//
// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s")
//
// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool {
return WithinDuration(a.t, expected, actual, delta, msgAndArgs...)
}
// Zero asserts that i is the zero value for its type and returns the truth.
func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool {
return Zero(a.t, i, msgAndArgs...)
}

@ -1,4 +0,0 @@
{{.CommentWithoutT "a"}}
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
}

Some files were not shown because too many files have changed in this diff Show More