Compare commits
21 Commits
v0.2-beta-
...
v0.2-beta-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
350bc670a6 | ||
|
|
82e60f7c72 | ||
|
|
28c02d882a | ||
|
|
95f70c084f | ||
|
|
2b85b0d1df | ||
|
|
88bafc701b | ||
|
|
f6bdcad085 | ||
|
|
e1b9475511 | ||
|
|
c52369e985 | ||
|
|
a8036659e4 | ||
|
|
91ebf2810c | ||
|
|
b35c50f7c5 | ||
|
|
4a1778ba69 | ||
|
|
c2db856049 | ||
|
|
1431ad46fd | ||
|
|
4a6654e08a | ||
|
|
3670d9fa20 | ||
|
|
7e74e9c844 | ||
|
|
6f748f4375 | ||
|
|
d80dbbe561 | ||
|
|
2abccb0257 |
@@ -90,6 +90,19 @@ follow the same general lifecycle. Differences are noted below.
|
||||
1. If you haven't already done so, sign a Contributor License Agreement
|
||||
(see details above).
|
||||
2. Fork the repository, develop and test your code changes.
|
||||
|
||||
You can use the following command to clone your fork to your local
|
||||
```
|
||||
cd $GOPATH
|
||||
mkdir -p {src,bin,pkg}
|
||||
mkdir -p src/github.com/virtual-kubelet/
|
||||
cd src/github.com/virtual-kubelet/
|
||||
git clone git@github.com:<your-github-account-name>/virtual-kubelet.git # OR: git clone https://github.com/<your-github-account-name>/virtual-kubelet.git
|
||||
cd virtual-kubelet
|
||||
go get ./...
|
||||
# add the virtual-kubelet as the upstream
|
||||
git remote add upstream git@github.com:virtual-kubelet/virtual-kubelet.git
|
||||
```
|
||||
3. Submit a pull request.
|
||||
|
||||
Your pull request will be reviewed according to the process defined in
|
||||
|
||||
@@ -17,6 +17,7 @@ RUN set -x \
|
||||
make \
|
||||
&& cd /go/src/github.com/virtual-kubelet/virtual-kubelet \
|
||||
&& make build \
|
||||
&& make test \
|
||||
&& apk del .build-deps \
|
||||
&& cp bin/virtual-kubelet /usr/bin/virtual-kubelet \
|
||||
&& rm -rf /go \
|
||||
|
||||
30
Gopkg.lock
generated
30
Gopkg.lock
generated
@@ -103,12 +103,6 @@
|
||||
revision = "5741799b275a3c4a5a9623a993576d7545cf7b5c"
|
||||
version = "v2.4.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/emicklei/go-restful-swagger12"
|
||||
packages = ["."]
|
||||
revision = "dcef7f55730566d41eae5db10e7d6981829720f6"
|
||||
version = "1.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/flynn/go-shlex"
|
||||
@@ -316,6 +310,12 @@
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/afero"
|
||||
packages = [".","mem"]
|
||||
@@ -352,6 +352,12 @@
|
||||
revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/vbatts/tar-split"
|
||||
packages = ["archive/tar","tar/asm","tar/storage"]
|
||||
@@ -414,8 +420,8 @@
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "k8s.io/api"
|
||||
packages = ["admissionregistration/v1alpha1","apps/v1beta1","apps/v1beta2","authentication/v1","authentication/v1beta1","authorization/v1","authorization/v1beta1","autoscaling/v1","autoscaling/v2beta1","batch/v1","batch/v1beta1","batch/v2alpha1","certificates/v1beta1","core/v1","extensions/v1beta1","networking/v1","policy/v1beta1","rbac/v1","rbac/v1alpha1","rbac/v1beta1","scheduling/v1alpha1","settings/v1alpha1","storage/v1","storage/v1beta1"]
|
||||
revision = "218912509d74a117d05a718bb926d0948e531c20"
|
||||
packages = ["admissionregistration/v1alpha1","admissionregistration/v1beta1","apps/v1","apps/v1beta1","apps/v1beta2","authentication/v1","authentication/v1beta1","authorization/v1","authorization/v1beta1","autoscaling/v1","autoscaling/v2beta1","batch/v1","batch/v1beta1","batch/v2alpha1","certificates/v1beta1","core/v1","events/v1beta1","extensions/v1beta1","networking/v1","policy/v1beta1","rbac/v1","rbac/v1alpha1","rbac/v1beta1","scheduling/v1alpha1","settings/v1alpha1","storage/v1","storage/v1alpha1","storage/v1beta1"]
|
||||
revision = "c4ebf33f93081f8c7602d29a59944e4a9f684ac2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -425,9 +431,9 @@
|
||||
|
||||
[[projects]]
|
||||
name = "k8s.io/client-go"
|
||||
packages = ["discovery","kubernetes","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/apps/v1beta1","kubernetes/typed/apps/v1beta2","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1beta1","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v2beta1","kubernetes/typed/batch/v1","kubernetes/typed/batch/v1beta1","kubernetes/typed/batch/v2alpha1","kubernetes/typed/certificates/v1beta1","kubernetes/typed/core/v1","kubernetes/typed/extensions/v1beta1","kubernetes/typed/networking/v1","kubernetes/typed/policy/v1beta1","kubernetes/typed/rbac/v1","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1beta1","kubernetes/typed/scheduling/v1alpha1","kubernetes/typed/settings/v1alpha1","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1beta1","pkg/version","rest","rest/watch","tools/auth","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/metrics","tools/reference","transport","util/cert","util/flowcontrol","util/homedir","util/integer"]
|
||||
revision = "2ae454230481a7cb5544325e12ad7658ecccd19b"
|
||||
version = "v5.0.1"
|
||||
packages = ["discovery","discovery/fake","kubernetes","kubernetes/fake","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/admissionregistration/v1alpha1/fake","kubernetes/typed/admissionregistration/v1beta1","kubernetes/typed/admissionregistration/v1beta1/fake","kubernetes/typed/apps/v1","kubernetes/typed/apps/v1/fake","kubernetes/typed/apps/v1beta1","kubernetes/typed/apps/v1beta1/fake","kubernetes/typed/apps/v1beta2","kubernetes/typed/apps/v1beta2/fake","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1/fake","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authentication/v1beta1/fake","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1/fake","kubernetes/typed/authorization/v1beta1","kubernetes/typed/authorization/v1beta1/fake","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v1/fake","kubernetes/typed/autoscaling/v2beta1","kubernetes/typed/autoscaling/v2beta1/fake","kubernetes/typed/batch/v1","kubernetes/typed/batch/v1/fake","kubernetes/typed/batch/v1beta1","kubernetes/typed/batch/v1beta1/fake","kubernetes/typed/batch/v2alpha1","kubernetes/typed/batch/v2alpha1/fake","kubernetes/typed/certificates/v1beta1","kubernetes/typed/certificates/v1beta1/fake","kubernetes/typed/core/v1","kubernetes/typed/core/v1/fake","kubernetes/typed/events/v1beta1","kubernetes/typed/events/v1beta1/fake","kubernetes/typed/extensions/v1beta1","kubernetes/typed/extensions/v1beta1/fake","kubernetes/typed/networking/v1","kubernetes/typed/networking/v1/fake","kubernetes/typed/policy/v1beta1","kubernetes/typed/policy/v1beta1/fake","kubernetes/typed/rbac/v1","kubernetes/typed/rbac/v1/fake","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1alpha1/fake","kubernetes/typed/rbac/v1beta1","kubernetes/typed/rbac/v1beta1/fake","kubernetes/typed/scheduling/v1alpha1","kubernetes/typed/scheduling/v1alpha1/fake","kubernetes/typed/settings/v1alpha1","kubernetes/typed/settings/v1alpha1/fake","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1/fake","kubernetes/typed/storage/v1alpha1","kubernetes/typed/storage/v1alpha1/fake","kubernetes/typed/storage/v1beta1","kubernetes/typed/storage/v1beta1/fake","pkg/version","rest","rest/watch","testing","tools/auth","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/metrics","tools/reference","transport","util/cert","util/flowcontrol","util/homedir","util/integer"]
|
||||
revision = "78700dec6369ba22221b72770783300f143df150"
|
||||
version = "v6.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -438,6 +444,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "febdb92c3131da3213797c6f20b297c751e9bddf5938064989494a21c3017333"
|
||||
inputs-digest = "23727e1f984f9e65550445f886df557794b7884e4dcfdb442f7eebec6f94f51c"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
@@ -56,3 +56,7 @@
|
||||
[[constraint]]
|
||||
name = "github.com/hyperhq/hypercli"
|
||||
revision = "3e4e4fa373cbf8672bded67688b4821cd30b37f9"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/client-go"
|
||||
version = "6.0.0"
|
||||
|
||||
6
Makefile
6
Makefile
@@ -61,17 +61,17 @@ clean: clean-build
|
||||
|
||||
test:
|
||||
@echo "Testing..."
|
||||
$Q go test $(if $V,-v) -i -race $(allpackages) # install -race libs to speed up next run
|
||||
$Q go test $(if $V,-v) -i $(allpackages) # install -race libs to speed up next run
|
||||
ifndef CI
|
||||
@echo "Testing Outside CI..."
|
||||
$Q go vet $(allpackages)
|
||||
$Q GODEBUG=cgocheck=2 go test -race $(allpackages)
|
||||
$Q GODEBUG=cgocheck=2 go test $(allpackages)
|
||||
else
|
||||
@echo "Testing in CI..."
|
||||
$Q mkdir -p test
|
||||
$Q ( go vet $(allpackages); echo $$? ) | \
|
||||
tee test/vet.txt | sed '$$ d'; exit $$(tail -1 test/vet.txt)
|
||||
$Q ( GODEBUG=cgocheck=2 go test -v -race $(allpackages); echo $$? ) | \
|
||||
$Q ( GODEBUG=cgocheck=2 go test -v $(allpackages); echo $$? ) | \
|
||||
tee test/output.txt | sed '$$ d'; exit $$(tail -1 test/output.txt)
|
||||
endif
|
||||
|
||||
|
||||
18
README.md
18
README.md
@@ -58,6 +58,7 @@ a `virtual-kubelet` node.
|
||||
* Environment variables
|
||||
* Public IPs
|
||||
* kubectl logs
|
||||
* DNS name labels
|
||||
|
||||
## Current Limitations
|
||||
|
||||
@@ -116,6 +117,10 @@ You can find detailed instructions on how to set it up and how to test it in the
|
||||
The Azure connector can use a configuration file specified by the `--provider-config` flag.
|
||||
The config file is in TOML format, and an example lives in `providers/azure/example.toml`.
|
||||
|
||||
#### More Details
|
||||
|
||||
See the [ACI Readme](providers/azure/README.md)
|
||||
|
||||
### Hyper.sh Provider
|
||||
|
||||
The Hyper.sh Provider allows Kubernetes clusters to deploy Hyper.sh containers
|
||||
@@ -197,14 +202,5 @@ Enable the ServiceNodeExclusion flag, by modifying the Controller Manager manife
|
||||
|
||||
## Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
Virtual Kubelet follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
Sign the [CNCF CLA](https://github.com/kubernetes/community/blob/master/CLA.md) to be able to make Pull Requests to this repo.
|
||||
|
||||
BIN
charts/virtual-kubelet-0.1.1.tgz
Normal file
BIN
charts/virtual-kubelet-0.1.1.tgz
Normal file
Binary file not shown.
Binary file not shown.
@@ -17,7 +17,7 @@ spec:
|
||||
- name: KUBELET_PORT
|
||||
value: "10250"
|
||||
- name: ACS_CREDENTIAL_LOCATION
|
||||
value: /etc/virtual-kubelet/acs.json
|
||||
value: /etc/acs/azure.json
|
||||
- name: AZURE_TENANT_ID
|
||||
value: {{ .Values.env.azureTenantId }}
|
||||
- name: AZURE_SUBSCRIPTION_ID
|
||||
@@ -45,7 +45,7 @@ spec:
|
||||
- name: credentials
|
||||
mountPath: "/etc/virtual-kubelet"
|
||||
- name: acs-credential
|
||||
mountPath: "/etc/virtual-kubelet/acs.json"
|
||||
mountPath: "/etc/acs/azure.json"
|
||||
command: ["virtual-kubelet"]
|
||||
args: ["--provider", "azure", "--namespace", "default", "--nodename", {{ default "virtual-kubelet" .Values.env.nodeName | quote }} , "--os", {{ default "Linux" .Values.env.nodeOsType | quote }}, "--taint", {{ default "azure.com/aci" .Values.env.nodeTaint | quote }}]
|
||||
volumes:
|
||||
@@ -55,3 +55,4 @@ spec:
|
||||
- name: acs-credential
|
||||
hostPath:
|
||||
path: /etc/kubernetes/azure.json
|
||||
type: File
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
image:
|
||||
repository: microsoft/virtual-kubelet
|
||||
tag: 0.2-beta-6
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
env:
|
||||
azureClientId:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: virtual-kubelet
|
||||
version: 0.1.0
|
||||
version: 0.1.1
|
||||
description: a Helm chart to install virtual kubelet inside a Kubernetes cluster.
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
|
||||
@@ -16,6 +16,6 @@ This deployment will be incomplete until all the required fields in the values.y
|
||||
To update, run:
|
||||
|
||||
helm upgrade {{ .Release.Name }} \
|
||||
--set env.azureClientId=<YOUR-AZURECLIENTID-HERE>,env.azureClientKey=<YOUR-AZURECLIENTKEY-HERE>,env.azureTenantId=<YOUR-AZURETENANTID-HERE>,env.azureSubscriptionId=<YOUR-AZURESUBSCRIPTIONID-HERE>,env.aciResourceGroup=<YOUR-ACIRESOURCEGROUP-HERE>,ev.aciOsType=<Linux|Windows>
|
||||
--set env.azureClientId=<YOUR-AZURECLIENTID-HERE>,env.azureClientKey=<YOUR-AZURECLIENTKEY-HERE>,env.azureTenantId=<YOUR-AZURETENANTID-HERE>,env.azureSubscriptionId=<YOUR-AZURESUBSCRIPTIONID-HERE>,env.aciResourceGroup=<YOUR-ACIRESOURCEGROUP-HERE>,ev.aciOsType=<Linux|Windows>,rbac.install=<false|true>
|
||||
|
||||
{{- end }}
|
||||
|
||||
14
charts/virtual-kubelet/templates/clusterrolebinding.yaml
Normal file
14
charts/virtual-kubelet/templates/clusterrolebinding.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
{{ if .Values.rbac.install }}
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ template "fullname" . }}-role-binding
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ template "fullname" . }}-service-account
|
||||
namespace: default
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ .Values.rbac.roleRef }}
|
||||
{{ end }}
|
||||
@@ -39,3 +39,4 @@ spec:
|
||||
- name: credentials
|
||||
secret:
|
||||
secretName: {{ template "fullname" . }}
|
||||
serviceAccountName: {{ if .Values.rbac.install }} "{{ template "fullname" . }}-service-account" {{ end }}
|
||||
|
||||
6
charts/virtual-kubelet/templates/serviceaccount.yaml
Normal file
6
charts/virtual-kubelet/templates/serviceaccount.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
{{ if .Values.rbac.install }}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ template "fullname" . }}-service-account
|
||||
{{ end }}
|
||||
@@ -14,3 +14,12 @@ env:
|
||||
nodeOsType:
|
||||
apiserverCert:
|
||||
apiserverKey:
|
||||
|
||||
# Install Default RBAC roles and bindings
|
||||
rbac:
|
||||
install: false
|
||||
serviceAccountName: virtual-kubelet
|
||||
# RBAC api version (currently v1beta1)
|
||||
apiVersion: v1beta1
|
||||
# Cluster role reference
|
||||
roleRef: cluster-admin
|
||||
|
||||
@@ -4,12 +4,12 @@ metadata:
|
||||
name: iis
|
||||
spec:
|
||||
containers:
|
||||
- image: microsoft/iis
|
||||
- image: microsoft/iis:nanoserver
|
||||
imagePullPolicy: Always
|
||||
name: iis
|
||||
resources:
|
||||
requests:
|
||||
memory: 4G
|
||||
memory: 3.5G
|
||||
cpu: 2
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -18,6 +18,7 @@ spec:
|
||||
- containerPort: 443
|
||||
name: https
|
||||
dnsPolicy: Default
|
||||
nodeName: virtual-kubelet-myaciconnector-win
|
||||
automountServiceAccountToken: false
|
||||
tolerations:
|
||||
- key: azure.com/aci
|
||||
|
||||
21
examples/nanoserver.yaml
Normal file
21
examples/nanoserver.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nano
|
||||
spec:
|
||||
containers:
|
||||
- image: microsoft/nanoserver:latest
|
||||
imagePullPolicy: Always
|
||||
command: ["powershell.exe", "-Command", "while ($true) { Sleep 5; Write-Host 'helloworld' }"]
|
||||
name: nanoserver
|
||||
resources:
|
||||
requests:
|
||||
memory: 1.5G
|
||||
cpu: 1
|
||||
dnsPolicy: Default
|
||||
restartPolicy: OnFailure
|
||||
nodeName: virtual-kubelet-myaciconnector-win
|
||||
automountServiceAccountToken: false
|
||||
tolerations:
|
||||
- key: azure.com/aci
|
||||
effect: NoSchedule
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
- containerPort: 443
|
||||
name: https
|
||||
dnsPolicy: ClusterFirst
|
||||
nodeName: virtual-kubelet
|
||||
nodeName: virtual-kubelet-myaciconnector-linux
|
||||
tolerations:
|
||||
- key: azure.com/aci
|
||||
effect: NoSchedule
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
// New ResourceManagers should be created with the NewResourceManager() function.
|
||||
type ResourceManager struct {
|
||||
sync.RWMutex
|
||||
k8sClient *kubernetes.Clientset
|
||||
k8sClient kubernetes.Interface
|
||||
|
||||
pods map[string]*v1.Pod
|
||||
configMapRef map[string]int64
|
||||
@@ -25,7 +25,7 @@ type ResourceManager struct {
|
||||
}
|
||||
|
||||
// NewResourceManager returns a ResourceManager with the internal maps initialized.
|
||||
func NewResourceManager(k8sClient *kubernetes.Clientset) *ResourceManager {
|
||||
func NewResourceManager(k8sClient kubernetes.Interface) *ResourceManager {
|
||||
rm := ResourceManager{
|
||||
pods: make(map[string]*v1.Pod, 0),
|
||||
configMapRef: make(map[string]int64, 0),
|
||||
@@ -44,7 +44,7 @@ func NewResourceManager(k8sClient *kubernetes.Clientset) *ResourceManager {
|
||||
rm.Lock()
|
||||
for n, c := range rm.secretRef {
|
||||
if c <= 0 {
|
||||
delete(rm.secrets, n)
|
||||
delete(rm.secretRef, n)
|
||||
}
|
||||
}
|
||||
for n := range rm.secrets {
|
||||
@@ -54,7 +54,7 @@ func NewResourceManager(k8sClient *kubernetes.Clientset) *ResourceManager {
|
||||
}
|
||||
for n, c := range rm.configMapRef {
|
||||
if c <= 0 {
|
||||
delete(rm.configMaps, n)
|
||||
delete(rm.configMapRef, n)
|
||||
}
|
||||
}
|
||||
for n := range rm.configMaps {
|
||||
@@ -230,7 +230,7 @@ func (rm *ResourceManager) watchConfigMaps() {
|
||||
// it evicts them from the internal cache
|
||||
func (rm *ResourceManager) watchSecrets() {
|
||||
var opts metav1.ListOptions
|
||||
w, err := rm.k8sClient.CoreV1().ConfigMaps(v1.NamespaceAll).Watch(opts)
|
||||
w, err := rm.k8sClient.CoreV1().Secrets(v1.NamespaceAll).Watch(opts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -245,9 +245,9 @@ func (rm *ResourceManager) watchSecrets() {
|
||||
rm.Lock()
|
||||
switch ev.Type {
|
||||
case watch.Modified:
|
||||
delete(rm.configMaps, ev.Object.(*v1.ConfigMap).Name)
|
||||
delete(rm.secrets, ev.Object.(*v1.Secret).Name)
|
||||
case watch.Deleted:
|
||||
delete(rm.configMaps, ev.Object.(*v1.ConfigMap).Name)
|
||||
delete(rm.secrets, ev.Object.(*v1.Secret).Name)
|
||||
}
|
||||
rm.Unlock()
|
||||
}
|
||||
|
||||
@@ -1,36 +1,21 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
var (
|
||||
fakeClient *kubernetes.Clientset
|
||||
fakeClient kubernetes.Interface
|
||||
)
|
||||
|
||||
func init() {
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
// if you want to change the loading rules (which files in which order), you can do so here
|
||||
|
||||
configOverrides := &clientcmd.ConfigOverrides{}
|
||||
// if you want to change override values or bind them to flags, there are methods to help you
|
||||
|
||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||
config, err := kubeConfig.ClientConfig()
|
||||
if err != nil {
|
||||
log.Fatal("unable to create client config")
|
||||
}
|
||||
fakeClient, err = kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal("unable to create new clientset")
|
||||
}
|
||||
fakeClient = fake.NewSimpleClientset()
|
||||
}
|
||||
|
||||
func TestResourceManager(t *testing.T) {
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
# Kubernetes virtual-kubelet with ACI
|
||||
# Kubernetes Virtual Kubelet with ACI
|
||||
|
||||
Azure Container Instances (ACI) provide a hosted environment for running containers in Azure. When using ACI, there is no need to manage the underlying compute infrastructure, Azure handles this management for you. When running containers in ACI, you are charged by the second for each running container.
|
||||
|
||||
The Azure Container Instances provider for the Virtual Kubelet configures an ACI instance as a node in any Kubernetes cluster. When using the Virtual Kubelet ACI provider, pods can be scheduled on an ACI instance as if the ACI instance is a standard Kubernetes node. This configuration allows you to take advantage of both the capabilities of Kubernetes and the management value and cost benefit of ACI.
|
||||
|
||||
This document details configuring the Virtual Kubelet ACI provider.
|
||||
#### Table of Contents
|
||||
|
||||
* [Prerequiste](#Prerequisite)
|
||||
* [Quick set-up with the ACI Connector](#Quick-set-up-with-the-ACI-Connector)
|
||||
* [Manual set-up](#Manual-set-up)
|
||||
* [Validate the Virtual Kubelet ACI provider](#Validate-the-Virtual-Kubelet-ACI-provider)
|
||||
* [Schedule a pod in ACI](#Schedule-a-pod-in-ACI)
|
||||
* [Upgrade the ACI Connector ](#Upgrade-the-ACI-Connector)
|
||||
* [Remove the Virtual Kubelet](#Remove-the-Virtual-Kubelet)
|
||||
## Prerequisite
|
||||
|
||||
This guide assumes that you have a Kubernetes cluster up and running (can be `minikube`) and that `kubectl` is already configured to talk to it.
|
||||
@@ -24,7 +32,7 @@ See the [full installation instructions](https://docs.microsoft.com/en-us/cli/az
|
||||
|
||||
#### MacOS
|
||||
|
||||
```console
|
||||
```cli
|
||||
brew install azure-cli
|
||||
```
|
||||
|
||||
@@ -35,12 +43,12 @@ Download and run the [Azure CLI Installer (MSI)](https://aka.ms/InstallAzureCliW
|
||||
#### Ubuntu 64-bit
|
||||
|
||||
1. Add the azure-cli repo to your sources:
|
||||
```console
|
||||
```cli
|
||||
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ wheezy main" | \
|
||||
sudo tee /etc/apt/sources.list.d/azure-cli.list
|
||||
```
|
||||
1. Run the following commands to install the Azure CLI and its dependencies:
|
||||
```console
|
||||
2. Run the following commands to install the Azure CLI and its dependencies:
|
||||
```cli
|
||||
sudo apt-key adv --keyserver packages.microsoft.com --recv-keys 52E16F86FEE04B979B07E28DB02C46DF417A0893
|
||||
sudo apt-get install apt-transport-https
|
||||
sudo apt-get update && sudo apt-get install azure-cli
|
||||
@@ -50,7 +58,7 @@ Download and run the [Azure CLI Installer (MSI)](https://aka.ms/InstallAzureCliW
|
||||
|
||||
Install `kubectl` by running the following command:
|
||||
|
||||
```console
|
||||
```cli
|
||||
az aks install-cli
|
||||
```
|
||||
|
||||
@@ -61,19 +69,19 @@ Install `helm` by running the following command:
|
||||
|
||||
#### MacOS
|
||||
|
||||
```console
|
||||
```cli
|
||||
brew install kubernetes-helm
|
||||
```
|
||||
|
||||
#### Windows
|
||||
|
||||
1. Download the latest [Helm release](https://storage.googleapis.com/kubernetes-helm/helm-v2.7.2-windows-amd64.tar.gz).
|
||||
1. Decompress the tar file.
|
||||
1. Copy **helm.exe** to a directory on your PATH.
|
||||
2. Decompress the tar file.
|
||||
3. Copy **helm.exe** to a directory on your PATH.
|
||||
|
||||
#### Linux
|
||||
|
||||
```console
|
||||
```cli
|
||||
curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | bash
|
||||
```
|
||||
---
|
||||
@@ -87,27 +95,61 @@ Now that we have all the tools, we will set up your Azure account to work with A
|
||||
First let's identify your Azure subscription and save it for use later on in the quickstart.
|
||||
|
||||
1. Run `az login` and follow the instructions in the command output to authorize `az` to use your account
|
||||
1. List your Azure subscriptions:
|
||||
```console
|
||||
2. List your Azure subscriptions:
|
||||
```cli
|
||||
az account list -o table
|
||||
```
|
||||
1. Copy your subscription ID and save it in an environment variable:
|
||||
3. Copy your subscription ID and save it in an environment variable:
|
||||
|
||||
**Bash**
|
||||
```console
|
||||
```cli
|
||||
export AZURE_SUBSCRIPTION_ID="<SubscriptionId>"
|
||||
```
|
||||
|
||||
**PowerShell**
|
||||
```console
|
||||
```cli
|
||||
$env:AZURE_SUBSCRIPTION_ID = "<SubscriptionId>"
|
||||
```
|
||||
|
||||
## Quick set-up with the ACI Connector
|
||||
|
||||
The Azure cli can be used to install the ACI provider. We like to say Azure's provider or implementation for Virtual Kubelet is the ACI Connector.
|
||||
For this section Virtual Kubelet's specific ACI provider will be referenced as the the ACI Connector.
|
||||
If you continue with this section you can skip sections below up to "Schedule a pod in ACI", as we use Azure Container Service (AKS) to easily deploy and install the connector, thus it is assumed
|
||||
that you've created an [AKS cluster](https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough).
|
||||
|
||||
To install the ACI Connector use the az cli and the aks namespace. Make sure to use the resource group of the aks cluster you've created and the name of the aks cluster you've created. You can choose the connector name to be anything. Choose any command below to install the Linux, Windows, or both the Windows and Linux Connector.
|
||||
|
||||
1. Install the Linux ACI Connector
|
||||
|
||||
**Bash**
|
||||
```cli
|
||||
az aks install-connector --resource-group <aks cluster rg> --name <aks cluster name> --os-type linux --connector-name myaciconnector
|
||||
```
|
||||
|
||||
2. Install the Windows ACI Connector
|
||||
|
||||
**Bash**
|
||||
```cli
|
||||
az aks install-connector --resource-group <aks cluster rg> --name <aks cluster name> --os-type windows --connector-name myaciconnector
|
||||
```
|
||||
|
||||
3. Install both the Windows and Linux ACI Connectors
|
||||
|
||||
**Bash**
|
||||
```cli
|
||||
az aks install-connector --resource-group <aks cluster rg> --name <aks cluster name> --os-type both --connector-name myaciconnector
|
||||
```
|
||||
|
||||
Now you are ready to deploy a pod to the connector so skip to the "Schedule a pod in ACI" section.
|
||||
|
||||
## Manual set-up
|
||||
|
||||
### Create a Resource Group for ACI
|
||||
|
||||
To use Azure Container Instances, you must provide a resource group. Create one with the az cli using the following command.
|
||||
|
||||
```console
|
||||
```cli
|
||||
export ACI_REGION=eastus
|
||||
az group create --name aci-group --location "$ACI_REGION"
|
||||
export AZURE_RG=aci-group
|
||||
@@ -119,20 +161,20 @@ This creates an identity for the Virtual Kubelet ACI provider to use when provis
|
||||
resources on your account on behalf of Kubernetes.
|
||||
|
||||
1. Create a service principal with RBAC enabled for the quickstart:
|
||||
```console
|
||||
```cli
|
||||
az ad sp create-for-rbac --name virtual-kubelet-quickstart -o table
|
||||
```
|
||||
1. Save the values from the command output in environment variables:
|
||||
2. Save the values from the command output in environment variables:
|
||||
|
||||
**Bash**
|
||||
```console
|
||||
```cli
|
||||
export AZURE_TENANT_ID=<Tenant>
|
||||
export AZURE_CLIENT_ID=<AppId>
|
||||
export AZURE_CLIENT_SECRET=<Password>
|
||||
```
|
||||
|
||||
**PowerShell**
|
||||
```console
|
||||
```cli
|
||||
$env:AZURE_TENANT_ID = "<Tenant>"
|
||||
$env:AZURE_CLIENT_ID = "<AppId>"
|
||||
$env:AZURE_CLIENT_SECRET = "<Password>"
|
||||
@@ -142,7 +184,7 @@ resources on your account on behalf of Kubernetes.
|
||||
|
||||
You will need to enable ACI in your subscription:
|
||||
|
||||
```console
|
||||
```cli
|
||||
az provider register -n Microsoft.ContainerInstance
|
||||
```
|
||||
|
||||
@@ -152,28 +194,31 @@ Run these commands to deploy the virtual kubelet which connects your Kubernetes
|
||||
|
||||
If your cluster is an AKS cluster:
|
||||
|
||||
```console
|
||||
```cli
|
||||
export VK_RELEASE=virtual-kubelet-for-aks-0.1.3
|
||||
````
|
||||
|
||||
For any other type of Kubernetes cluster:
|
||||
|
||||
```console
|
||||
export VK_RELEASE=virtual-kubelet-0.1.0
|
||||
```cli
|
||||
export VK_RELEASE=virtual-kubelet-0.1.1
|
||||
```
|
||||
|
||||
```console
|
||||
```cli
|
||||
RELEASE_NAME=virtual-kubelet
|
||||
NODE_NAME=virtual-kubelet
|
||||
CHART_URL=https://github.com/virtual-kubelet/virtual-kubelet/raw/master/charts/$VK_RELEASE.tgz
|
||||
|
||||
curl https://raw.githubusercontent.com/virtual-kubelet/virtual-kubelet/master/scripts/createCertAndKey.sh > createCertAndKey.sh
|
||||
. createCertAndKey.sh
|
||||
chmod +x createCertAndKey.sh
|
||||
. ./createCertAndKey.sh
|
||||
|
||||
helm install "$CHART_URL" --name "$RELEASE_NAME" \
|
||||
--set env.azureClientId="$AZURE_CLIENT_ID",env.azureClientKey="$AZURE_CLIENT_SECRET",env.azureTenantId="$AZURE_TENANT_ID",env.azureSubscriptionId="$AZURE_SUBSCRIPTION_ID",env.aciResourceGroup="$AZURE_RG",env.nodeName="$NODE_NAME",env.nodeOsType=<Linux|Windows>,env.apiserverCert=$cert,env.apiserverKey=$key
|
||||
--set env.azureClientId="$AZURE_CLIENT_ID",env.azureClientKey="$AZURE_CLIENT_SECRET",env.azureTenantId="$AZURE_TENANT_ID",env.azureSubscriptionId="$AZURE_SUBSCRIPTION_ID",env.aciRegion="$ACI_REGION",env.aciResourceGroup="$AZURE_RG",env.nodeName="$NODE_NAME",env.nodeOsType=<Linux|Windows>,env.apiserverCert=$cert,env.apiserverKey=$key,rbac.install=false
|
||||
```
|
||||
|
||||
If your cluster has RBAC enabled set ```rbac.install=true```
|
||||
|
||||
Output:
|
||||
|
||||
```console
|
||||
@@ -201,6 +246,7 @@ The virtual kubelet is getting deployed on your cluster.
|
||||
|
||||
To verify that virtual kubelet has started, run:
|
||||
|
||||
```cli
|
||||
kubectl --namespace=default get pods -l "app=virtual-kubelet-virtual-kubelet"
|
||||
```
|
||||
|
||||
@@ -208,7 +254,7 @@ To verify that virtual kubelet has started, run:
|
||||
|
||||
To validate that the Virtual Kubelet has been installed, return a list of Kubernetes nodes using the [kubectl get nodes][kubectl-get] command. You should see a node that matches the name given to the ACI connector.
|
||||
|
||||
```azurecli-interactive
|
||||
```cli
|
||||
kubectl get nodes
|
||||
```
|
||||
|
||||
@@ -216,7 +262,7 @@ Output:
|
||||
|
||||
```console
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
virtual-kubelet Ready <none> 2m v1.8.3
|
||||
virtual-kubelet-myconnector-linux Ready <none> 2m v1.8.3
|
||||
aks-nodepool1-39289454-0 Ready agent 22h v1.7.7
|
||||
aks-nodepool1-39289454-1 Ready agent 22h v1.7.7
|
||||
aks-nodepool1-39289454-2 Ready agent 22h v1.7.7
|
||||
@@ -247,18 +293,21 @@ spec:
|
||||
- containerPort: 443
|
||||
name: https
|
||||
dnsPolicy: ClusterFirst
|
||||
nodeName: virtual-kubelet
|
||||
nodeName: virtual-kubelet-myconnector-linux
|
||||
tolerations:
|
||||
- key: azure.com/aci
|
||||
effect: NoSchedule
|
||||
```
|
||||
|
||||
Run the application with the [kubectl create][kubectl-create] command.
|
||||
|
||||
```console
|
||||
```cli
|
||||
kubectl create -f virtual-kubelet-test.yml
|
||||
```
|
||||
|
||||
Use the [kubectl get pods][kubectl-get] command with the `-o wide` argument to output a list of pods with the scheduled node.
|
||||
|
||||
```console
|
||||
```cli
|
||||
kubectl get pods -o wide
|
||||
```
|
||||
|
||||
@@ -283,16 +332,76 @@ Name ResourceGroup ProvisioningState Image
|
||||
------------------------------- --------------- ------------------- ------------------------ --------------- --------------- -------- ----------
|
||||
helloworld-2559879000-8vmjw myResourceGroup Succeeded microsoft/aci-helloworld 52.179.3.180:80 1.0 core/1.5 gb Linux eastus
|
||||
```
|
||||
<!--
|
||||
### Schedule an ACI pod with a DNS Name label
|
||||
|
||||
Add an annotation to your Pod manifest, `virtualkubelet.io/dnsnamelabel` keyed to what you'd like the Azure Container Instance to receive as a DNS Name, and deploy it.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: helloworld
|
||||
annotations:
|
||||
virtualkubelet.io/dnsnamelabel: "helloworld-aci"
|
||||
spec:
|
||||
containers:
|
||||
- image: microsoft/aci-helloworld
|
||||
imagePullPolicy: Always
|
||||
name: helloworld
|
||||
resources:
|
||||
requests:
|
||||
memory: 1G
|
||||
cpu: 1
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
protocol: TCP
|
||||
- containerPort: 443
|
||||
name: https
|
||||
dnsPolicy: ClusterFirst
|
||||
nodeName: virtual-kubelet
|
||||
```
|
||||
|
||||
To confirm the Azure Container Instance received and bound the DNS Name specified, use the [az container show][az-container-show] Azure CLI command. Virtual Kubelet's naming
|
||||
convention will affect how you use this query, with the argument to `-n` broken down as: nameSpace-podName. Unless specified, Kubernetes will assume
|
||||
the namespace is `default`.
|
||||
|
||||
```azurecli-interactive
|
||||
az container show -g myResourceGroup -n default-helloworld --query ipAddress.fqdn
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```console
|
||||
"helloworld-aci.westus.azurecontainer.io"
|
||||
```
|
||||
-->
|
||||
|
||||
## Upgrade the ACI Connector
|
||||
|
||||
If you've installed Virtual Kubelet with the Azure cli so you're using the ACI Connector implementation, then you are also able to upgrade the connector to the latest release.
|
||||
Run the following command to upgrade your ACI Connector.
|
||||
|
||||
```cli
|
||||
az aks upgrade-connector --resource-group <aks cluster rg> --name <aks cluster name> --connector-name myconnector --os-type linux
|
||||
```
|
||||
|
||||
## Remove the Virtual Kubelet
|
||||
|
||||
You can remove your Virtual Kubelet node, you can delete the Helm deployment, by running the following command:
|
||||
You can remove your Virtual Kubelet node by deleting the Helm deployment. Run the following command:
|
||||
|
||||
```
|
||||
```cli
|
||||
helm delete virtual-kubelet --purge
|
||||
```
|
||||
```
|
||||
If you used the ACI Connector installation then use the following command to remove the the ACI Connector from your cluster.
|
||||
|
||||
```cli
|
||||
az aks remove-connector --resource-group <aks cluster rg> --name <aks cluster name> --connector-name myconnector --os-type linux
|
||||
```
|
||||
|
||||
<!-- LINKS -->
|
||||
[kubectl-create]: https://kubernetes.io/docs/user-guide/kubectl/v1.6/#create
|
||||
[kubectl-get]: https://kubernetes.io/docs/user-guide/kubectl/v1.8/#get
|
||||
[az-container-list]: https://docs.microsoft.com/en-us/cli/azure/aks?view=azure-cli-latest#az_aks_list
|
||||
[az-container-list]: https://docs.microsoft.com/en-us/cli/azure/container?view=azure-cli-latest#az_container_list
|
||||
[az-container-show]: https://docs.microsoft.com/en-us/cli/azure/container?view=azure-cli-latest#az_container_show
|
||||
|
||||
68
providers/azure/aadMock.go
Normal file
68
providers/azure/aadMock.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
)
|
||||
|
||||
// AADMock implements a AAD mock server .
|
||||
type AADMock struct {
|
||||
server *httptest.Server
|
||||
OnAcquireToken func(http.ResponseWriter,*http.Request)
|
||||
}
|
||||
|
||||
// NewAADMock creates a new AAD server mocker.
|
||||
func NewAADMock() *AADMock {
|
||||
aadServer := new(AADMock)
|
||||
aadServer.start()
|
||||
|
||||
return aadServer
|
||||
}
|
||||
|
||||
// Start the AAD server mocker.
|
||||
func (mock *AADMock)start() {
|
||||
if mock.server != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mock.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if mock.OnAcquireToken != nil {
|
||||
mock.OnAcquireToken(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
token := adal.Token{
|
||||
AccessToken: "Test Token",
|
||||
NotBefore: strconv.FormatInt(time.Now().UnixNano(), 10),
|
||||
ExpiresIn: strconv.FormatInt(int64(time.Minute), 10),
|
||||
}
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
json.NewEncoder(b).Encode(token)
|
||||
w.Write(b.Bytes())
|
||||
}))
|
||||
}
|
||||
|
||||
// GetServerURL returns the mock server URL.
|
||||
func (mock *AADMock)GetServerURL() string {
|
||||
if mock.server != nil {
|
||||
return mock.server.URL
|
||||
}
|
||||
|
||||
panic("Mock server is not initialized.")
|
||||
}
|
||||
|
||||
// Close terminates the AAD server mocker.
|
||||
func (mock *AADMock)Close() {
|
||||
if mock.server != nil {
|
||||
mock.server.Close()
|
||||
mock.server = nil
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import (
|
||||
// The service account secret mount path.
|
||||
const serviceAccountSecretMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
|
||||
|
||||
const virtualKubeletDNSNameLabel = "virtualkubelet.io/dnsnamelabel"
|
||||
|
||||
// ACIProvider implements the virtual-kubelet provider interface and communicates with Azure's ACI APIs.
|
||||
type ACIProvider struct {
|
||||
aciClient *aci.Client
|
||||
@@ -52,12 +54,13 @@ type AuthConfig struct {
|
||||
RegistryToken string `json:"registrytoken,omitempty"`
|
||||
}
|
||||
|
||||
// See https://docs.microsoft.com/en-us/azure/container-instances/container-instances-quotas for valid regions.
|
||||
// See https://azure.microsoft.com/en-us/status/ for valid regions.
|
||||
var validAciRegions = []string{
|
||||
"westeurope",
|
||||
"westus",
|
||||
"eastus",
|
||||
"southeastasia",
|
||||
"westus2",
|
||||
}
|
||||
|
||||
// isValidACIRegion checks to make sure we're using a valid ACI region
|
||||
@@ -231,6 +234,10 @@ func (p *ACIProvider) CreatePod(pod *v1.Pod) error {
|
||||
Ports: ports,
|
||||
Type: "Public",
|
||||
}
|
||||
|
||||
if dnsNameLabel := pod.Annotations[virtualKubeletDNSNameLabel]; dnsNameLabel != "" {
|
||||
containerGroup.ContainerGroupProperties.IPAddress.DNSNameLabel = dnsNameLabel
|
||||
}
|
||||
}
|
||||
|
||||
podUID := string(pod.UID)
|
||||
@@ -446,28 +453,80 @@ func (p *ACIProvider) getImagePullSecrets(pod *v1.Pod) ([]aci.ImageRegistryCrede
|
||||
// TODO: Check if secret type is v1.SecretTypeDockercfg and use DockerConfigKey instead of hardcoded value
|
||||
// TODO: Check if secret type is v1.SecretTypeDockerConfigJson and use DockerConfigJsonKey to determine if it's in json format
|
||||
// TODO: Return error if it's not one of these two types
|
||||
repoData, ok := secret.Data[".dockercfg"]
|
||||
if !ok {
|
||||
return ips, fmt.Errorf("no dockercfg present in secret")
|
||||
switch secret.Type {
|
||||
case v1.SecretTypeDockercfg:
|
||||
ips, err = readDockerCfgSecret(secret, ips)
|
||||
case v1.SecretTypeDockerConfigJson:
|
||||
ips, err = readDockerConfigJSONSecret(secret, ips)
|
||||
default:
|
||||
return nil, fmt.Errorf("image pull secret type is not one of kubernetes.io/dockercfg or kubernetes.io/dockerconfigjson")
|
||||
}
|
||||
|
||||
var authConfigs map[string]AuthConfig
|
||||
err = json.Unmarshal(repoData, &authConfigs)
|
||||
if err != nil {
|
||||
return ips, err
|
||||
}
|
||||
|
||||
for server, authConfig := range authConfigs {
|
||||
ips = append(ips, aci.ImageRegistryCredential{
|
||||
Password: authConfig.Password,
|
||||
Server: server,
|
||||
Username: authConfig.Username,
|
||||
})
|
||||
}
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
func readDockerCfgSecret(secret *v1.Secret, ips []aci.ImageRegistryCredential) ([]aci.ImageRegistryCredential, error) {
|
||||
var err error
|
||||
var authConfigs map[string]AuthConfig
|
||||
repoData, ok := secret.Data[string(v1.DockerConfigKey)]
|
||||
|
||||
if !ok {
|
||||
return ips, fmt.Errorf("no dockercfg present in secret")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(repoData, &authConfigs)
|
||||
if err != nil {
|
||||
return ips, err
|
||||
}
|
||||
|
||||
for server, authConfig := range authConfigs {
|
||||
ips = append(ips, aci.ImageRegistryCredential{
|
||||
Password: authConfig.Password,
|
||||
Server: server,
|
||||
Username: authConfig.Username,
|
||||
})
|
||||
}
|
||||
|
||||
return ips, err
|
||||
}
|
||||
|
||||
func readDockerConfigJSONSecret(secret *v1.Secret, ips []aci.ImageRegistryCredential) ([]aci.ImageRegistryCredential, error) {
|
||||
var err error
|
||||
repoData, ok := secret.Data[string(v1.DockerConfigJsonKey)]
|
||||
|
||||
if !ok {
|
||||
return ips, fmt.Errorf("no dockerconfigjson present in secret")
|
||||
}
|
||||
|
||||
var authConfigs map[string]map[string]AuthConfig
|
||||
|
||||
err = json.Unmarshal(repoData, &authConfigs)
|
||||
if err != nil {
|
||||
return ips, err
|
||||
}
|
||||
|
||||
auths, ok := authConfigs["auths"]
|
||||
|
||||
if !ok {
|
||||
return ips, fmt.Errorf("malformed dockerconfigjson in secret")
|
||||
}
|
||||
|
||||
for server, authConfig := range auths {
|
||||
ips = append(ips, aci.ImageRegistryCredential{
|
||||
Password: authConfig.Password,
|
||||
Server: server,
|
||||
Username: authConfig.Username,
|
||||
})
|
||||
}
|
||||
|
||||
return ips, err
|
||||
}
|
||||
|
||||
func (p *ACIProvider) getContainers(pod *v1.Pod) ([]aci.Container, error) {
|
||||
containers := make([]aci.Container, 0, len(pod.Spec.Containers))
|
||||
for _, container := range pod.Spec.Containers {
|
||||
@@ -504,10 +563,41 @@ func (p *ACIProvider) getContainers(pod *v1.Pod) ([]aci.Container, error) {
|
||||
})
|
||||
}
|
||||
|
||||
cpuLimit := float64(container.Resources.Limits.Cpu().Value())
|
||||
memoryLimit := float64(container.Resources.Limits.Memory().Value()) / 1000000000.00
|
||||
cpuRequest := float64(container.Resources.Requests.Cpu().Value())
|
||||
memoryRequest := float64(container.Resources.Requests.Memory().Value()) / 1000000000.00
|
||||
// NOTE(robbiezhang): ACI CPU limit must be times of 10m
|
||||
cpuLimit := 1.00
|
||||
if _, ok := container.Resources.Limits[v1.ResourceCPU]; ok {
|
||||
cpuLimit = float64(container.Resources.Limits.Cpu().MilliValue() / 10.00) / 100.00
|
||||
if cpuLimit < 0.01 {
|
||||
cpuLimit = 0.01
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(robbiezhang): ACI Memory limit must be times of 0.1 GB
|
||||
memoryLimit := 1.50
|
||||
if _, ok := container.Resources.Limits[v1.ResourceMemory]; ok {
|
||||
memoryLimit = float64(container.Resources.Limits.Memory().Value() / 100000000.00) / 10.00
|
||||
if memoryLimit < 0.10 {
|
||||
memoryLimit = 0.10
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(robbiezhang): ACI CPU request must be times of 10m
|
||||
cpuRequest := 1.00
|
||||
if _, ok := container.Resources.Requests[v1.ResourceCPU]; ok {
|
||||
cpuRequest = float64(container.Resources.Requests.Cpu().MilliValue() / 10.00) / 100.00
|
||||
if cpuRequest < 0.01 {
|
||||
cpuRequest = 0.01
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(robbiezhang): ACI memory request must be times of 0.1 GB
|
||||
memoryRequest := 1.50
|
||||
if _, ok := container.Resources.Requests[v1.ResourceMemory]; ok {
|
||||
memoryRequest = float64(container.Resources.Requests.Memory().Value() / 100000000.00) / 10.00
|
||||
if memoryRequest < 0.10 {
|
||||
memoryRequest = 0.10
|
||||
}
|
||||
}
|
||||
|
||||
c.Resources = aci.ResourceRequirements{
|
||||
Limits: aci.ResourceLimits{
|
||||
|
||||
83
providers/azure/aciMock.go
Normal file
83
providers/azure/aciMock.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/aci"
|
||||
)
|
||||
|
||||
// ACIMock implements a Azure Container Instance mock server.
|
||||
type ACIMock struct {
|
||||
server *httptest.Server
|
||||
OnCreate func(string, string, string, *aci.ContainerGroup) (int, interface{})
|
||||
}
|
||||
|
||||
const (
|
||||
containerGroupsRoute = "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.ContainerInstance/containerGroups"
|
||||
containerGroupRoute = containerGroupsRoute + "/{containerGroup}"
|
||||
containerGroupLogRoute = containerGroupRoute + "/containers/{containerName}/logs"
|
||||
)
|
||||
|
||||
// NewACIMock creates a new Azure Container Instance mock server.
|
||||
func NewACIMock() *ACIMock {
|
||||
mock := new(ACIMock)
|
||||
mock.start()
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// Start the Azure Container Instance mock service.
|
||||
func (mock *ACIMock) start() {
|
||||
if mock.server != nil {
|
||||
return
|
||||
}
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc(
|
||||
containerGroupRoute,
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
subscription, _ := mux.Vars(r)["subscriptionId"]
|
||||
resourceGroup, _ := mux.Vars(r)["resourceGroup"]
|
||||
containerGroup, _ := mux.Vars(r)["containerGroup"]
|
||||
|
||||
var cg aci.ContainerGroup
|
||||
if err := json.NewDecoder(r.Body).Decode(&cg); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if mock.OnCreate != nil {
|
||||
statusCode, response := mock.OnCreate(subscription, resourceGroup, containerGroup, &cg)
|
||||
w.WriteHeader(statusCode)
|
||||
b := new(bytes.Buffer)
|
||||
json.NewEncoder(b).Encode(response)
|
||||
w.Write(b.Bytes())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}).Methods("PUT")
|
||||
|
||||
mock.server = httptest.NewServer(router)
|
||||
}
|
||||
|
||||
// GetServerURL returns the mock server URL.
|
||||
func (mock *ACIMock) GetServerURL() string {
|
||||
if mock.server != nil {
|
||||
return mock.server.URL
|
||||
}
|
||||
|
||||
panic("Mock server is not initialized.")
|
||||
}
|
||||
|
||||
// Close terminates the Azure Container Instance mock server.
|
||||
func (mock *ACIMock) Close() {
|
||||
if mock.server != nil {
|
||||
mock.server.Close()
|
||||
mock.server = nil
|
||||
}
|
||||
}
|
||||
183
providers/azure/aci_test.go
Normal file
183
providers/azure/aci_test.go
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||
azure "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/aci"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
const (
|
||||
fakeSubscription = "a88d9e8f-3cb3-456f-8f10-27395c1e122a"
|
||||
fakeResourceGroup = "vk-rg"
|
||||
fakeClientID = "f14193ad-4c4c-4876-a18a-c0badb3bbd40"
|
||||
fakeClientSecret = "VGhpcyBpcyBhIHNlY3JldAo="
|
||||
fakeTenantID = "8cb81aca-83fe-4c6f-b667-4ec09c45a8bf"
|
||||
)
|
||||
|
||||
// Tests create pod without resource spec
|
||||
func TestCreatePodWithoutResourceSpec(t *testing.T) {
|
||||
_, aciServerMocker, provider, err := prepareMocks()
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("Unable to prepare the mocks", err)
|
||||
}
|
||||
|
||||
podName := "pod-" + uuid.New().String()
|
||||
podNamespace := "ns-" + uuid.New().String()
|
||||
|
||||
aciServerMocker.OnCreate = func(subscription, resourceGroup, containerGroup string, cg *aci.ContainerGroup) (int, interface{}) {
|
||||
assert.Equal(t, fakeSubscription, subscription, "Subscription doesn't match")
|
||||
assert.Equal(t, fakeResourceGroup, resourceGroup, "Resource group doesn't match")
|
||||
assert.NotNil(t, cg, "Container group is nil")
|
||||
assert.Equal(t, podNamespace + "-" + podName, containerGroup, "Container group name is not expected")
|
||||
assert.NotNil(t, cg.ContainerGroupProperties, "Container group properties should not be nil")
|
||||
assert.NotNil(t, cg.ContainerGroupProperties.Containers, "Containers should not be nil")
|
||||
assert.Equal(t, 1, len(cg.ContainerGroupProperties.Containers), "1 Container is expected")
|
||||
assert.Equal(t, "nginx", cg.ContainerGroupProperties.Containers[0].Name, "Container nginx is expected")
|
||||
assert.NotNil(t, cg.ContainerGroupProperties.Containers[0].Resources, "Container resources should not be nil")
|
||||
assert.NotNil(t, cg.ContainerGroupProperties.Containers[0].Resources.Requests, "Container resource requests should not be nil")
|
||||
assert.Equal(t, 1.0, cg.ContainerGroupProperties.Containers[0].Resources.Requests.CPU, "Request CPU is not expected")
|
||||
assert.Equal(t, 1.5, cg.ContainerGroupProperties.Containers[0].Resources.Requests.MemoryInGB, "Request Memory is not expected")
|
||||
assert.Equal(t, 1.0, cg.ContainerGroupProperties.Containers[0].Resources.Limits.CPU, "Limit CPU is not expected")
|
||||
assert.Equal(t, 1.5, cg.ContainerGroupProperties.Containers[0].Resources.Limits.MemoryInGB, "Limit Memory is not expected")
|
||||
|
||||
return http.StatusOK, cg
|
||||
}
|
||||
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Namespace: podNamespace,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Name: "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := provider.CreatePod(pod); err != nil {
|
||||
t.Fatal("Failed to create pod", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests create pod without resource spec
|
||||
func TestCreatePodWithResourceSpec(t *testing.T) {
|
||||
_, aciServerMocker, provider, err := prepareMocks()
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("Unable to prepare the mocks", err)
|
||||
}
|
||||
|
||||
podName := "pod-" + uuid.New().String()
|
||||
podNamespace := "ns-" + uuid.New().String()
|
||||
|
||||
aciServerMocker.OnCreate = func(subscription, resourceGroup, containerGroup string, cg *aci.ContainerGroup) (int, interface{}) {
|
||||
assert.Equal(t, fakeSubscription, subscription, "Subscription doesn't match")
|
||||
assert.Equal(t, fakeResourceGroup, resourceGroup, "Resource group doesn't match")
|
||||
assert.NotNil(t, cg, "Container group is nil")
|
||||
assert.Equal(t, podNamespace + "-" + podName, containerGroup, "Container group name is not expected")
|
||||
assert.NotNil(t, cg.ContainerGroupProperties, "Container group properties should not be nil")
|
||||
assert.NotNil(t, cg.ContainerGroupProperties.Containers, "Containers should not be nil")
|
||||
assert.Equal(t, 1, len(cg.ContainerGroupProperties.Containers), "1 Container is expected")
|
||||
assert.Equal(t, "nginx", cg.ContainerGroupProperties.Containers[0].Name, "Container nginx is expected")
|
||||
assert.NotNil(t, cg.ContainerGroupProperties.Containers[0].Resources, "Container resources should not be nil")
|
||||
assert.NotNil(t, cg.ContainerGroupProperties.Containers[0].Resources.Requests, "Container resource requests should not be nil")
|
||||
assert.Equal(t, 1.98, cg.ContainerGroupProperties.Containers[0].Resources.Requests.CPU, "Request CPU is not expected")
|
||||
assert.Equal(t, 3.4, cg.ContainerGroupProperties.Containers[0].Resources.Requests.MemoryInGB, "Request Memory is not expected")
|
||||
assert.Equal(t, 3.99, cg.ContainerGroupProperties.Containers[0].Resources.Limits.CPU, "Limit CPU is not expected")
|
||||
assert.Equal(t, 8.0, cg.ContainerGroupProperties.Containers[0].Resources.Limits.MemoryInGB, "Limit Memory is not expected")
|
||||
|
||||
return http.StatusOK, cg
|
||||
}
|
||||
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Namespace: podNamespace,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Name: "nginx",
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
"cpu": resource.MustParse("1.98"),
|
||||
"memory": resource.MustParse("3.4G"),
|
||||
},
|
||||
Limits: v1.ResourceList{
|
||||
"cpu": resource.MustParse("3999m"),
|
||||
"memory": resource.MustParse("8010M"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := provider.CreatePod(pod); err != nil {
|
||||
t.Fatal("Failed to create pod", err)
|
||||
}
|
||||
}
|
||||
|
||||
func prepareMocks() (*AADMock, *ACIMock, *ACIProvider, error) {
|
||||
aadServerMocker := NewAADMock()
|
||||
aciServerMocker := NewACIMock()
|
||||
|
||||
auth := azure.NewAuthentication(
|
||||
azure.PublicCloud.Name,
|
||||
fakeClientID,
|
||||
fakeClientSecret,
|
||||
fakeSubscription,
|
||||
fakeTenantID)
|
||||
|
||||
auth.ActiveDirectoryEndpoint = aadServerMocker.GetServerURL()
|
||||
auth.ResourceManagerEndpoint = aciServerMocker.GetServerURL()
|
||||
|
||||
file, err := ioutil.TempFile("", "auth.json")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
json.NewEncoder(b).Encode(auth)
|
||||
|
||||
if _, err := file.Write(b.Bytes()); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
os.Setenv("AZURE_AUTH_LOCATION", file.Name())
|
||||
os.Setenv("ACI_RESOURCE_GROUP", fakeResourceGroup)
|
||||
|
||||
clientset := fake.NewSimpleClientset()
|
||||
rm := manager.NewResourceManager(clientset)
|
||||
|
||||
provider, err := NewACIProvider("example.toml", rm, "vk", "Linux", "0.0.0.0", 10250)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return aadServerMocker, aciServerMocker, provider, nil
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
|
||||
const (
|
||||
// BaseURI is the default URI used for compute services.
|
||||
BaseURI = "https://management.azure.com"
|
||||
userAgent = "virtual-kubelet/azure-arm-aci/2017-12-01"
|
||||
apiVersion = "2017-12-01-preview"
|
||||
baseURI = "https://management.azure.com"
|
||||
userAgent = "virtual-kubelet/azure-arm-aci/2018-02-01"
|
||||
apiVersion = "2018-02-01-preview"
|
||||
|
||||
containerGroupURLPath = "subscriptions/{{.subscriptionId}}/resourceGroups/{{.resourceGroup}}/providers/Microsoft.ContainerInstance/containerGroups/{{.containerGroupName}}"
|
||||
containerGroupListURLPath = "subscriptions/{{.subscriptionId}}/providers/Microsoft.ContainerInstance/containerGroups"
|
||||
@@ -34,7 +34,7 @@ func NewClient(auth *azure.Authentication) (*Client, error) {
|
||||
return nil, fmt.Errorf("Authentication is not supplied for the Azure client")
|
||||
}
|
||||
|
||||
client, err := azure.NewClient(auth, BaseURI, userAgent)
|
||||
client, err := azure.NewClient(auth, baseURI, userAgent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating Azure client failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func (c *Client) CreateContainerGroup(resourceGroup, containerGroupName string,
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(BaseURI, containerGroupURLPath)
|
||||
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the body for the request.
|
||||
|
||||
@@ -17,7 +17,7 @@ func (c *Client) DeleteContainerGroup(resourceGroup, containerGroupName string)
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(BaseURI, containerGroupURLPath)
|
||||
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
|
||||
@@ -19,7 +19,7 @@ func (c *Client) GetContainerGroup(resourceGroup, containerGroupName string) (*C
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(BaseURI, containerGroupURLPath)
|
||||
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
|
||||
@@ -22,10 +22,10 @@ func (c *Client) ListContainerGroups(resourceGroup string) (*ContainerGroupListR
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(BaseURI, containerGroupListURLPath)
|
||||
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupListURLPath)
|
||||
// List by resource group if they passed one.
|
||||
if resourceGroup != "" {
|
||||
uri = api.ResolveRelative(BaseURI, containerGroupListByResourceGroupURLPath)
|
||||
uri = api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerGroupListByResourceGroupURLPath)
|
||||
|
||||
}
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
@@ -20,7 +20,7 @@ func (c *Client) GetContainerLogs(resourceGroup, containerGroupName, containerNa
|
||||
}
|
||||
|
||||
// Create the url.
|
||||
uri := api.ResolveRelative(BaseURI, containerLogsURLPath)
|
||||
uri := api.ResolveRelative(c.auth.ResourceManagerEndpoint, containerLogsURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
// Create the request.
|
||||
|
||||
@@ -172,9 +172,10 @@ type ImageRegistryCredential struct {
|
||||
|
||||
// IPAddress is IP address for the container group.
|
||||
type IPAddress struct {
|
||||
Ports []Port `json:"ports,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
Ports []Port `json:"ports,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
DNSNameLabel string `json:"dnsNameLabel,omitempty"`
|
||||
}
|
||||
|
||||
// Logs is the logs.
|
||||
|
||||
@@ -77,9 +77,9 @@ func (p *MockProvider) DeletePod(pod *v1.Pod) (err error) {
|
||||
|
||||
// GetPod returns a pod by name that is stored in memory.
|
||||
func (p *MockProvider) GetPod(namespace, name string) (pod *v1.Pod, err error) {
|
||||
log.Printf("receive GetPod %q\n", pod.Name)
|
||||
log.Printf("receive GetPod %q\n", name)
|
||||
|
||||
key, err := buildKey(pod)
|
||||
key, err := buildKeyFromNames(namespace, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -246,6 +246,10 @@ func (p *MockProvider) OperatingSystem() string {
|
||||
return providers.OperatingSystemLinux
|
||||
}
|
||||
|
||||
func buildKeyFromNames(namespace string, name string) (string, error) {
|
||||
return fmt.Sprintf("%s-%s", namespace, name), nil
|
||||
}
|
||||
|
||||
// buildKey is a helper for building the "key" for the providers pod store.
|
||||
func buildKey(pod *v1.Pod) (string, error) {
|
||||
if pod.ObjectMeta.Namespace == "" {
|
||||
@@ -256,5 +260,5 @@ func buildKey(pod *v1.Pod) (string, error) {
|
||||
return "", fmt.Errorf("pod name not found")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s-%s", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name), nil
|
||||
return buildKeyFromNames(pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
|
||||
}
|
||||
|
||||
157
providers/web/README.md
Normal file
157
providers/web/README.md
Normal file
@@ -0,0 +1,157 @@
|
||||
Web provider for Virtual Kubelet
|
||||
================================
|
||||
|
||||
Virtual Kubelet providers are written using the Go programming language. While
|
||||
Go is a great general purpose programming language, it is however a fact that
|
||||
other programming languages exist. The problem that Virtual Kubelet solves is as
|
||||
applicable to applications written in those languages as it is for those written
|
||||
using Go. This provider aims to serve as a bridge between technology stacks and
|
||||
programming languages, as it were, by adapting the Virtual Kubelet provider
|
||||
interface using a web endpoint, i.e., this provider is a thin layer that
|
||||
forwards all calls that Kubernetes makes to the virtual kubelet to a
|
||||
pre-configured HTTP endpoint. This frees the provider's implementor to write
|
||||
their code in any programming language and technology stack as they see fit.
|
||||
|
||||
The `providers/web/web-rust` folder contains a sample provider implemented in
|
||||
the Rust programming language. Here's a diagram that depicts the interaction
|
||||
between Kubernetes, the virtual kubelet web provider and the Rust app.
|
||||
|
||||
+----------------+ +---------------------------+ +------------------------------+
|
||||
| | | | HTTP | |
|
||||
| Kubernetes | <-----> | Virtual Kubelet: Web | <------> | Provider written in Rust |
|
||||
| | | | | |
|
||||
+----------------+ +---------------------------+ +------------------------------+
|
||||
|
||||
Provider interface
|
||||
------------------
|
||||
|
||||
The web provider uses an environment variable to determine the endpoint to use
|
||||
for forwarding requests. The environment variable must be named
|
||||
`WEB_ENDPOINT_URL` and must implement the following HTTP API:
|
||||
|
||||
| Path | Verb | Query | Request | Response | Description |
|
||||
|-------------------|--------|-----------------------------------------|----------|---------------------------------------------------|---------------------------------------------------------------------------|
|
||||
| /createPod | POST | - | Pod JSON | HTTP status code | Create a new pod |
|
||||
| /updatePod | PUT | - | Pod JSON | HTTP status code | Update pod spec |
|
||||
| /deletePod | DELETE | - | Pod JSON | HTTP status code | Delete an existing pod |
|
||||
| /getPod | GET | namespace, name | - | Pod JSON | Given a pod namespace and name, return the pod JSON |
|
||||
| /getContainerLogs | GET | namespace, podName, containerName, tail | - | Container logs | Given the namespace, pod name and container name, return `tail` log lines |
|
||||
| /getPodStatus | GET | namespace, name | - | Pod status JSON | Given a pod namespace and name, return the pod's status JSON |
|
||||
| /getPods | GET | - | - | Array of pod JSON strings | Fetch list of created pods |
|
||||
| /capacity | GET | - | - | JSON map containing resource name and values | Fetch resource capacity values |
|
||||
| /nodeConditions | GET | - | - | Array of node condition JSON strings | Get list of node conditions (Ready, OutOfDisk etc) |
|
||||
| /nodeAddresses | GET | - | - | Array of node address values (type/address pairs) | Fetch a list of addresses for the node status |
|
||||
|
||||
A typical deployment configuration for this setup would be to have the provider
|
||||
implementation be deployed as a container in the same pod as the virtual kubelet
|
||||
itself (as a "sidecar").
|
||||
|
||||
Take her for a spin
|
||||
-------------------
|
||||
|
||||
A sample web provider implementation is included in this repo in order to
|
||||
showcase what this enables. The sample has been implemented in
|
||||
[Rust](http://rust-lang.org). The easiest way to get this up and running is
|
||||
to use the Helm chart available at `providers/web/charts/web-rust`. Open a
|
||||
terminal and install the chart like so:
|
||||
|
||||
$ cd providers/web/charts
|
||||
$ helm install -n web-provider ./web-rust
|
||||
|
||||
$ kubectl get pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
web-provider-virtual-kubelet-web-6b5b7446f6-279xl 2/2 Running 0 3h
|
||||
|
||||
If you list the nodes in the cluster after this you should see something that
|
||||
looks like this:
|
||||
|
||||
$ kubectl get nodes
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
aks-nodepool1-35187879-0 Ready agent 37d v1.8.2
|
||||
aks-nodepool1-35187879-1 Ready agent 37d v1.8.2
|
||||
aks-nodepool1-35187879-3 Ready agent 37d v1.8.2
|
||||
virtual-kubelet-web Ready agent 3h v1.8.3
|
||||
|
||||
In case the name of the node didn't give it away, the last entry in the output
|
||||
above is the virtual kubelet. If you try to list the containers in the pod that
|
||||
represents the virtual kubelet you should be able to see the sidecar Rust
|
||||
container:
|
||||
|
||||
$ kubectl get pods -o=custom-columns=NAME:.metadata.name,CONTAINERS:.spec.containers[*].name
|
||||
NAME CONTAINERS
|
||||
web-provider-virtual-kubelet-web-6b5b7446f6-279xl webrust,virtualkubelet
|
||||
|
||||
In the output above, `webrust` is the sidecar container and `virtualkubelet` is
|
||||
the broker that forwards requests to `webrust`. You can run a query on the
|
||||
`/getPods` HTTP endpoint on the `webrust` container to see a list of the pods
|
||||
that it has been asked to create. To do this we first use `kubectl` to setup a
|
||||
port forwarding server like so:
|
||||
|
||||
$ kubectl port-forward web-provider-virtual-kubelet-web-6b5b7446f6-279xl 3000:3000
|
||||
|
||||
Now if we run `curl` on the `http://localhost:3000/getPods` URL you should see
|
||||
the pod JSON getting dumped to the terminal. I ran my test on a Kubernetes
|
||||
cluster deployed on [Azure](https://docs.microsoft.com/en-us/azure/aks/) which
|
||||
happens to deploy a daemonset with some, what I imagine are "system" pods to
|
||||
every node in the cluster. You can filter the output to see just the pod names
|
||||
using the [jq](https://stedolan.github.io/jq/) tool like so:
|
||||
|
||||
$ curl -s http://localhost:3000/getPods | jq '.[] | { name: .metadata.name }'
|
||||
{
|
||||
"name": "kube-proxy-czz57"
|
||||
}
|
||||
{
|
||||
"name": "kube-svc-redirect-7qlpd"
|
||||
}
|
||||
|
||||
You can deploy workloads to the virtual kubelet as you normally do. Here's a
|
||||
sample pod spec that uses `nodeSelector` to cause the deployment to be scheduled
|
||||
on the virtual kubelet.
|
||||
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: vk-pod
|
||||
labels:
|
||||
foo: bar
|
||||
spec:
|
||||
containers:
|
||||
- name: web1
|
||||
image: nginx
|
||||
nodeSelector:
|
||||
type: virtual-kubelet
|
||||
|
||||
Let's go ahead and deploy the pod and run our `/getPods` query again:
|
||||
|
||||
$ kubectl apply -f ~/tmp/pod1.yaml
|
||||
pod "vk-pod" created
|
||||
|
||||
$ curl -s http://localhost:3000/getPods | jq '.[] | { name: .metadata.name }'
|
||||
{
|
||||
"name": "kube-proxy-czz57"
|
||||
}
|
||||
{
|
||||
"name": "kube-svc-redirect-7qlpd"
|
||||
}
|
||||
{
|
||||
"name": "vk-pod"
|
||||
}
|
||||
|
||||
$ kubectl get pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
vk-pod 0/1 Running 0 1m
|
||||
web-provider-virtual-kubelet-web-6b5b7446f6-279xl 2/2 Running 6 4h
|
||||
|
||||
As you can tell, a new pod has been scheduled to run on our virtual kubelet
|
||||
instance. Deleting pods works as one would expect:
|
||||
|
||||
$ kubectl delete -f ~/tmp/pod1.yaml
|
||||
pod "vk-pod" deleted
|
||||
|
||||
$ curl -s http://localhost:3000/getPods | jq '.[] | { name: .metadata.name }'
|
||||
{
|
||||
"name": "kube-proxy-czz57"
|
||||
}
|
||||
{
|
||||
"name": "kube-svc-redirect-7qlpd"
|
||||
}
|
||||
@@ -19,7 +19,7 @@ spec:
|
||||
release: {{ .Release.Name }}
|
||||
spec:
|
||||
containers:
|
||||
- name: rustwebprovider
|
||||
- name: webrust
|
||||
image: "{{ .Values.rustwebimage.repository }}:{{ .Values.rustwebimage.tag }}"
|
||||
imagePullPolicy: {{ .Values.rustwebimage.pullPolicy }}
|
||||
ports:
|
||||
@@ -1,5 +1,5 @@
|
||||
rustwebimage:
|
||||
repository: avranju/rust-web-provider
|
||||
repository: avranju/web-rust
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
port: 3000
|
||||
@@ -1,9 +0,0 @@
|
||||
FROM debian:stretch-slim
|
||||
|
||||
WORKDIR /app
|
||||
ADD ./rust-web-provider /app/rust-web-provider
|
||||
|
||||
ENV RUST_LOG=info
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT [ "/app/rust-web-provider" ]
|
||||
@@ -558,17 +558,6 @@ dependencies = [
|
||||
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-web-provider"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kube_rust 1.0.0 (git+https://github.com/avranju/kube-rust?rev=058de6366d0d75cb60b2d0fd5ba1abd2e7d83fff)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"virtual-kubelet-adapter 0.1.0 (git+https://github.com/avranju/rust-virtual-kubelet-adapter?rev=4250103d31e2864725e47bdd23295e79ee12b6d0)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.24"
|
||||
@@ -852,6 +841,17 @@ name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "web-rust"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kube_rust 1.0.0 (git+https://github.com/avranju/kube-rust?rev=058de6366d0d75cb60b2d0fd5ba1abd2e7d83fff)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"virtual-kubelet-adapter 0.1.0 (git+https://github.com/avranju/rust-virtual-kubelet-adapter?rev=4250103d31e2864725e47bdd23295e79ee12b6d0)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "rust-web-provider"
|
||||
name = "web-rust"
|
||||
version = "0.1.0"
|
||||
authors = ["Rajasekharan Vengalil <rajave@microsoft.com>"]
|
||||
|
||||
9
providers/web/web-rust/Dockerfile
Normal file
9
providers/web/web-rust/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM debian:stretch-slim
|
||||
|
||||
WORKDIR /app
|
||||
ADD ./web-rust /app/web-rust
|
||||
|
||||
ENV RUST_LOG=info
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT [ "/app/web-rust" ]
|
||||
@@ -6,8 +6,8 @@ SCRIPT_NAME=$(basename "$0")
|
||||
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
ROOT_FOLDER="$DIR/.."
|
||||
PUBLISH_DIR=$ROOT_FOLDER/target/publish
|
||||
TARGET_NAME=rust-web-provider
|
||||
IMAGE_NAME=rust-web-provider
|
||||
TARGET_NAME=web-rust
|
||||
IMAGE_NAME=web-rust
|
||||
IMAGE_VERSION=latest
|
||||
BUILD_RELEASE=true
|
||||
SOURCE_RELEASE_DIR=$ROOT_FOLDER/target/release
|
||||
@@ -20,7 +20,7 @@ usage()
|
||||
echo "Note: You might have to run this as root or sudo."
|
||||
echo ""
|
||||
echo "options"
|
||||
echo " -i, --image-name Image name (default: rust-web-provider)"
|
||||
echo " -i, --image-name Image name (default: web-rust)"
|
||||
echo " -v, --image-version Docker Image Version (default: latest)"
|
||||
echo " -r, --build-release Build release configuration - true|false (default: true)"
|
||||
exit 1;
|
||||
@@ -74,7 +74,7 @@ impl Provider for UnitProvider {
|
||||
info!("Getting pod: {}", name);
|
||||
self.pods_map
|
||||
.get(name)
|
||||
.filter(|pod| {
|
||||
.xfilter(|pod| {
|
||||
let empty = String::from("");
|
||||
let ns = pod.metadata()
|
||||
.map(|m| m.namespace())
|
||||
@@ -1,9 +1,9 @@
|
||||
pub trait Filter<T> {
|
||||
fn filter<P: FnOnce(&T) -> bool>(self, predicate: P) -> Self;
|
||||
fn xfilter<P: FnOnce(&T) -> bool>(self, predicate: P) -> Self;
|
||||
}
|
||||
|
||||
impl<T> Filter<T> for Option<T> {
|
||||
fn filter<P: FnOnce(&T) -> bool>(self, predicate: P) -> Self {
|
||||
fn xfilter<P: FnOnce(&T) -> bool>(self, predicate: P) -> Self {
|
||||
match self {
|
||||
Some(x) => {
|
||||
if predicate(&x) {
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Generate cert and key for chart
|
||||
openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem -subj "/C=US/ST=CA/L=virtualkubelet/O=virtualkubelet/OU=virtualkubelet/CN=virtualkubelet"
|
||||
cert=$(base64 cert.pem)
|
||||
key=$(base64 key.pem)
|
||||
cert=$(base64 cert.pem -w0)
|
||||
key=$(base64 key.pem -w0)
|
||||
|
||||
6
scripts/devenv.ps1
Normal file
6
scripts/devenv.ps1
Normal file
@@ -0,0 +1,6 @@
|
||||
$pwd = (Get-Location).Path
|
||||
|
||||
docker run --security-opt seccomp:unconfined -it `
|
||||
-v ${pwd}:/gopath/src/github.com/virtual-kubelet/virtual-kubelet `
|
||||
-w /gopath/src/github.com/virtual-kubelet/virtual-kubelet `
|
||||
golang /bin/bash
|
||||
46
vendor/github.com/emicklei/go-restful-swagger12/CHANGES.md
generated
vendored
46
vendor/github.com/emicklei/go-restful-swagger12/CHANGES.md
generated
vendored
@@ -1,46 +0,0 @@
|
||||
Change history of swagger
|
||||
=
|
||||
2017-01-30
|
||||
- moved from go-restful/swagger to go-restful-swagger12
|
||||
|
||||
2015-10-16
|
||||
- add type override mechanism for swagger models (MR 254, nathanejohnson)
|
||||
- replace uses of wildcard in generated apidocs (issue 251)
|
||||
|
||||
2015-05-25
|
||||
- (api break) changed the type of Properties in Model
|
||||
- (api break) changed the type of Models in ApiDeclaration
|
||||
- (api break) changed the parameter type of PostBuildDeclarationMapFunc
|
||||
|
||||
2015-04-09
|
||||
- add ModelBuildable interface for customization of Model
|
||||
|
||||
2015-03-17
|
||||
- preserve order of Routes per WebService in Swagger listing
|
||||
- fix use of $ref and type in Swagger models
|
||||
- add api version to listing
|
||||
|
||||
2014-11-14
|
||||
- operation parameters are now sorted using ordering path,query,form,header,body
|
||||
|
||||
2014-11-12
|
||||
- respect omitempty tag value for embedded structs
|
||||
- expose ApiVersion of WebService to Swagger ApiDeclaration
|
||||
|
||||
2014-05-29
|
||||
- (api add) Ability to define custom http.Handler to serve swagger-ui static files
|
||||
|
||||
2014-05-04
|
||||
- (fix) include model for array element type of response
|
||||
|
||||
2014-01-03
|
||||
- (fix) do not add primitive type to the Api models
|
||||
|
||||
2013-11-27
|
||||
- (fix) make Swagger work for WebServices with root ("/" or "") paths
|
||||
|
||||
2013-10-29
|
||||
- (api add) package variable LogInfo to customize logging function
|
||||
|
||||
2013-10-15
|
||||
- upgraded to spec version 1.2 (https://github.com/wordnik/swagger-core/wiki/1.2-transition)
|
||||
22
vendor/github.com/emicklei/go-restful-swagger12/LICENSE
generated
vendored
22
vendor/github.com/emicklei/go-restful-swagger12/LICENSE
generated
vendored
@@ -1,22 +0,0 @@
|
||||
Copyright (c) 2017 Ernest Micklei
|
||||
|
||||
MIT License
|
||||
|
||||
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.
|
||||
83
vendor/github.com/emicklei/go-restful-swagger12/README.md
generated
vendored
83
vendor/github.com/emicklei/go-restful-swagger12/README.md
generated
vendored
@@ -1,83 +0,0 @@
|
||||
# go-restful-swagger12
|
||||
|
||||
[](https://travis-ci.org/emicklei/go-restful-swagger12)
|
||||
[](https://godoc.org/github.com/emicklei/go-restful-swagger12)
|
||||
|
||||
How to use Swagger UI with go-restful
|
||||
=
|
||||
|
||||
Get the Swagger UI sources (version 1.2 only)
|
||||
|
||||
git clone https://github.com/wordnik/swagger-ui.git
|
||||
|
||||
The project contains a "dist" folder.
|
||||
Its contents has all the Swagger UI files you need.
|
||||
|
||||
The `index.html` has an `url` set to `http://petstore.swagger.wordnik.com/api/api-docs`.
|
||||
You need to change that to match your WebService JSON endpoint e.g. `http://localhost:8080/apidocs.json`
|
||||
|
||||
Now, you can install the Swagger WebService for serving the Swagger specification in JSON.
|
||||
|
||||
config := swagger.Config{
|
||||
WebServices: restful.RegisteredWebServices(),
|
||||
ApiPath: "/apidocs.json",
|
||||
SwaggerPath: "/apidocs/",
|
||||
SwaggerFilePath: "/Users/emicklei/Projects/swagger-ui/dist"}
|
||||
swagger.InstallSwaggerService(config)
|
||||
|
||||
|
||||
Documenting Structs
|
||||
--
|
||||
|
||||
Currently there are 2 ways to document your structs in the go-restful Swagger.
|
||||
|
||||
###### By using struct tags
|
||||
- Use tag "description" to annotate a struct field with a description to show in the UI
|
||||
- Use tag "modelDescription" to annotate the struct itself with a description to show in the UI. The tag can be added in an field of the struct and in case that there are multiple definition, they will be appended with an empty line.
|
||||
|
||||
###### By using the SwaggerDoc method
|
||||
Here is an example with an `Address` struct and the documentation for each of the fields. The `""` is a special entry for **documenting the struct itself**.
|
||||
|
||||
type Address struct {
|
||||
Country string `json:"country,omitempty"`
|
||||
PostCode int `json:"postcode,omitempty"`
|
||||
}
|
||||
|
||||
func (Address) SwaggerDoc() map[string]string {
|
||||
return map[string]string{
|
||||
"": "Address doc",
|
||||
"country": "Country doc",
|
||||
"postcode": "PostCode doc",
|
||||
}
|
||||
}
|
||||
|
||||
This example will generate a JSON like this
|
||||
|
||||
{
|
||||
"Address": {
|
||||
"id": "Address",
|
||||
"description": "Address doc",
|
||||
"properties": {
|
||||
"country": {
|
||||
"type": "string",
|
||||
"description": "Country doc"
|
||||
},
|
||||
"postcode": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "PostCode doc"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
**Very Important Notes:**
|
||||
- `SwaggerDoc()` is using a **NON-Pointer** receiver (e.g. func (Address) and not func (*Address))
|
||||
- The returned map should use as key the name of the field as defined in the JSON parameter (e.g. `"postcode"` and not `"PostCode"`)
|
||||
|
||||
Notes
|
||||
--
|
||||
- The Nickname of an Operation is automatically set by finding the name of the function. You can override it using RouteBuilder.Operation(..)
|
||||
- The WebServices field of swagger.Config can be used to control which service you want to expose and document ; you can have multiple configs and therefore multiple endpoints.
|
||||
|
||||
© 2017, ernestmicklei.com. MIT License. Contributions welcome.
|
||||
64
vendor/github.com/emicklei/go-restful-swagger12/api_declaration_list.go
generated
vendored
64
vendor/github.com/emicklei/go-restful-swagger12/api_declaration_list.go
generated
vendored
@@ -1,64 +0,0 @@
|
||||
package swagger
|
||||
|
||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// ApiDeclarationList maintains an ordered list of ApiDeclaration.
|
||||
type ApiDeclarationList struct {
|
||||
List []ApiDeclaration
|
||||
}
|
||||
|
||||
// At returns the ApiDeclaration by its path unless absent, then ok is false
|
||||
func (l *ApiDeclarationList) At(path string) (a ApiDeclaration, ok bool) {
|
||||
for _, each := range l.List {
|
||||
if each.ResourcePath == path {
|
||||
return each, true
|
||||
}
|
||||
}
|
||||
return a, false
|
||||
}
|
||||
|
||||
// Put adds or replaces a ApiDeclaration with this name
|
||||
func (l *ApiDeclarationList) Put(path string, a ApiDeclaration) {
|
||||
// maybe replace existing
|
||||
for i, each := range l.List {
|
||||
if each.ResourcePath == path {
|
||||
// replace
|
||||
l.List[i] = a
|
||||
return
|
||||
}
|
||||
}
|
||||
// add
|
||||
l.List = append(l.List, a)
|
||||
}
|
||||
|
||||
// Do enumerates all the properties, each with its assigned name
|
||||
func (l *ApiDeclarationList) Do(block func(path string, decl ApiDeclaration)) {
|
||||
for _, each := range l.List {
|
||||
block(each.ResourcePath, each)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON writes the ModelPropertyList as if it was a map[string]ModelProperty
|
||||
func (l ApiDeclarationList) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
encoder := json.NewEncoder(&buf)
|
||||
buf.WriteString("{\n")
|
||||
for i, each := range l.List {
|
||||
buf.WriteString("\"")
|
||||
buf.WriteString(each.ResourcePath)
|
||||
buf.WriteString("\": ")
|
||||
encoder.Encode(each)
|
||||
if i < len(l.List)-1 {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
buf.WriteString("}")
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
46
vendor/github.com/emicklei/go-restful-swagger12/config.go
generated
vendored
46
vendor/github.com/emicklei/go-restful-swagger12/config.go
generated
vendored
@@ -1,46 +0,0 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
)
|
||||
|
||||
// PostBuildDeclarationMapFunc can be used to modify the api declaration map.
|
||||
type PostBuildDeclarationMapFunc func(apiDeclarationMap *ApiDeclarationList)
|
||||
|
||||
// MapSchemaFormatFunc can be used to modify typeName at definition time.
|
||||
type MapSchemaFormatFunc func(typeName string) string
|
||||
|
||||
// MapModelTypeNameFunc can be used to return the desired typeName for a given
|
||||
// type. It will return false if the default name should be used.
|
||||
type MapModelTypeNameFunc func(t reflect.Type) (string, bool)
|
||||
|
||||
type Config struct {
|
||||
// url where the services are available, e.g. http://localhost:8080
|
||||
// if left empty then the basePath of Swagger is taken from the actual request
|
||||
WebServicesUrl string
|
||||
// path where the JSON api is avaiable , e.g. /apidocs
|
||||
ApiPath string
|
||||
// [optional] path where the swagger UI will be served, e.g. /swagger
|
||||
SwaggerPath string
|
||||
// [optional] location of folder containing Swagger HTML5 application index.html
|
||||
SwaggerFilePath string
|
||||
// api listing is constructed from this list of restful WebServices.
|
||||
WebServices []*restful.WebService
|
||||
// will serve all static content (scripts,pages,images)
|
||||
StaticHandler http.Handler
|
||||
// [optional] on default CORS (Cross-Origin-Resource-Sharing) is enabled.
|
||||
DisableCORS bool
|
||||
// Top-level API version. Is reflected in the resource listing.
|
||||
ApiVersion string
|
||||
// If set then call this handler after building the complete ApiDeclaration Map
|
||||
PostBuildHandler PostBuildDeclarationMapFunc
|
||||
// Swagger global info struct
|
||||
Info Info
|
||||
// [optional] If set, model builder should call this handler to get addition typename-to-swagger-format-field conversion.
|
||||
SchemaFormatHandler MapSchemaFormatFunc
|
||||
// [optional] If set, model builder should call this handler to retrieve the name for a given type.
|
||||
ModelTypeNameHandler MapModelTypeNameFunc
|
||||
}
|
||||
467
vendor/github.com/emicklei/go-restful-swagger12/model_builder.go
generated
vendored
467
vendor/github.com/emicklei/go-restful-swagger12/model_builder.go
generated
vendored
@@ -1,467 +0,0 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ModelBuildable is used for extending Structs that need more control over
|
||||
// how the Model appears in the Swagger api declaration.
|
||||
type ModelBuildable interface {
|
||||
PostBuildModel(m *Model) *Model
|
||||
}
|
||||
|
||||
type modelBuilder struct {
|
||||
Models *ModelList
|
||||
Config *Config
|
||||
}
|
||||
|
||||
type documentable interface {
|
||||
SwaggerDoc() map[string]string
|
||||
}
|
||||
|
||||
// Check if this structure has a method with signature func (<theModel>) SwaggerDoc() map[string]string
|
||||
// If it exists, retrive the documentation and overwrite all struct tag descriptions
|
||||
func getDocFromMethodSwaggerDoc2(model reflect.Type) map[string]string {
|
||||
if docable, ok := reflect.New(model).Elem().Interface().(documentable); ok {
|
||||
return docable.SwaggerDoc()
|
||||
}
|
||||
return make(map[string]string)
|
||||
}
|
||||
|
||||
// addModelFrom creates and adds a Model to the builder and detects and calls
|
||||
// the post build hook for customizations
|
||||
func (b modelBuilder) addModelFrom(sample interface{}) {
|
||||
if modelOrNil := b.addModel(reflect.TypeOf(sample), ""); modelOrNil != nil {
|
||||
// allow customizations
|
||||
if buildable, ok := sample.(ModelBuildable); ok {
|
||||
modelOrNil = buildable.PostBuildModel(modelOrNil)
|
||||
b.Models.Put(modelOrNil.Id, *modelOrNil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b modelBuilder) addModel(st reflect.Type, nameOverride string) *Model {
|
||||
// Turn pointers into simpler types so further checks are
|
||||
// correct.
|
||||
if st.Kind() == reflect.Ptr {
|
||||
st = st.Elem()
|
||||
}
|
||||
|
||||
modelName := b.keyFrom(st)
|
||||
if nameOverride != "" {
|
||||
modelName = nameOverride
|
||||
}
|
||||
// no models needed for primitive types
|
||||
if b.isPrimitiveType(modelName) {
|
||||
return nil
|
||||
}
|
||||
// golang encoding/json packages says array and slice values encode as
|
||||
// JSON arrays, except that []byte encodes as a base64-encoded string.
|
||||
// If we see a []byte here, treat it at as a primitive type (string)
|
||||
// and deal with it in buildArrayTypeProperty.
|
||||
if (st.Kind() == reflect.Slice || st.Kind() == reflect.Array) &&
|
||||
st.Elem().Kind() == reflect.Uint8 {
|
||||
return nil
|
||||
}
|
||||
// see if we already have visited this model
|
||||
if _, ok := b.Models.At(modelName); ok {
|
||||
return nil
|
||||
}
|
||||
sm := Model{
|
||||
Id: modelName,
|
||||
Required: []string{},
|
||||
Properties: ModelPropertyList{}}
|
||||
|
||||
// reference the model before further initializing (enables recursive structs)
|
||||
b.Models.Put(modelName, sm)
|
||||
|
||||
// check for slice or array
|
||||
if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
|
||||
b.addModel(st.Elem(), "")
|
||||
return &sm
|
||||
}
|
||||
// check for structure or primitive type
|
||||
if st.Kind() != reflect.Struct {
|
||||
return &sm
|
||||
}
|
||||
|
||||
fullDoc := getDocFromMethodSwaggerDoc2(st)
|
||||
modelDescriptions := []string{}
|
||||
|
||||
for i := 0; i < st.NumField(); i++ {
|
||||
field := st.Field(i)
|
||||
jsonName, modelDescription, prop := b.buildProperty(field, &sm, modelName)
|
||||
if len(modelDescription) > 0 {
|
||||
modelDescriptions = append(modelDescriptions, modelDescription)
|
||||
}
|
||||
|
||||
// add if not omitted
|
||||
if len(jsonName) != 0 {
|
||||
// update description
|
||||
if fieldDoc, ok := fullDoc[jsonName]; ok {
|
||||
prop.Description = fieldDoc
|
||||
}
|
||||
// update Required
|
||||
if b.isPropertyRequired(field) {
|
||||
sm.Required = append(sm.Required, jsonName)
|
||||
}
|
||||
sm.Properties.Put(jsonName, prop)
|
||||
}
|
||||
}
|
||||
|
||||
// We always overwrite documentation if SwaggerDoc method exists
|
||||
// "" is special for documenting the struct itself
|
||||
if modelDoc, ok := fullDoc[""]; ok {
|
||||
sm.Description = modelDoc
|
||||
} else if len(modelDescriptions) != 0 {
|
||||
sm.Description = strings.Join(modelDescriptions, "\n")
|
||||
}
|
||||
|
||||
// update model builder with completed model
|
||||
b.Models.Put(modelName, sm)
|
||||
|
||||
return &sm
|
||||
}
|
||||
|
||||
func (b modelBuilder) isPropertyRequired(field reflect.StructField) bool {
|
||||
required := true
|
||||
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
|
||||
s := strings.Split(jsonTag, ",")
|
||||
if len(s) > 1 && s[1] == "omitempty" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return required
|
||||
}
|
||||
|
||||
func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, modelName string) (jsonName, modelDescription string, prop ModelProperty) {
|
||||
jsonName = b.jsonNameOfField(field)
|
||||
if len(jsonName) == 0 {
|
||||
// empty name signals skip property
|
||||
return "", "", prop
|
||||
}
|
||||
|
||||
if field.Name == "XMLName" && field.Type.String() == "xml.Name" {
|
||||
// property is metadata for the xml.Name attribute, can be skipped
|
||||
return "", "", prop
|
||||
}
|
||||
|
||||
if tag := field.Tag.Get("modelDescription"); tag != "" {
|
||||
modelDescription = tag
|
||||
}
|
||||
|
||||
prop.setPropertyMetadata(field)
|
||||
if prop.Type != nil {
|
||||
return jsonName, modelDescription, prop
|
||||
}
|
||||
fieldType := field.Type
|
||||
|
||||
// check if type is doing its own marshalling
|
||||
marshalerType := reflect.TypeOf((*json.Marshaler)(nil)).Elem()
|
||||
if fieldType.Implements(marshalerType) {
|
||||
var pType = "string"
|
||||
if prop.Type == nil {
|
||||
prop.Type = &pType
|
||||
}
|
||||
if prop.Format == "" {
|
||||
prop.Format = b.jsonSchemaFormat(b.keyFrom(fieldType))
|
||||
}
|
||||
return jsonName, modelDescription, prop
|
||||
}
|
||||
|
||||
// check if annotation says it is a string
|
||||
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
|
||||
s := strings.Split(jsonTag, ",")
|
||||
if len(s) > 1 && s[1] == "string" {
|
||||
stringt := "string"
|
||||
prop.Type = &stringt
|
||||
return jsonName, modelDescription, prop
|
||||
}
|
||||
}
|
||||
|
||||
fieldKind := fieldType.Kind()
|
||||
switch {
|
||||
case fieldKind == reflect.Struct:
|
||||
jsonName, prop := b.buildStructTypeProperty(field, jsonName, model)
|
||||
return jsonName, modelDescription, prop
|
||||
case fieldKind == reflect.Slice || fieldKind == reflect.Array:
|
||||
jsonName, prop := b.buildArrayTypeProperty(field, jsonName, modelName)
|
||||
return jsonName, modelDescription, prop
|
||||
case fieldKind == reflect.Ptr:
|
||||
jsonName, prop := b.buildPointerTypeProperty(field, jsonName, modelName)
|
||||
return jsonName, modelDescription, prop
|
||||
case fieldKind == reflect.String:
|
||||
stringt := "string"
|
||||
prop.Type = &stringt
|
||||
return jsonName, modelDescription, prop
|
||||
case fieldKind == reflect.Map:
|
||||
// if it's a map, it's unstructured, and swagger 1.2 can't handle it
|
||||
objectType := "object"
|
||||
prop.Type = &objectType
|
||||
return jsonName, modelDescription, prop
|
||||
}
|
||||
|
||||
fieldTypeName := b.keyFrom(fieldType)
|
||||
if b.isPrimitiveType(fieldTypeName) {
|
||||
mapped := b.jsonSchemaType(fieldTypeName)
|
||||
prop.Type = &mapped
|
||||
prop.Format = b.jsonSchemaFormat(fieldTypeName)
|
||||
return jsonName, modelDescription, prop
|
||||
}
|
||||
modelType := b.keyFrom(fieldType)
|
||||
prop.Ref = &modelType
|
||||
|
||||
if fieldType.Name() == "" { // override type of anonymous structs
|
||||
nestedTypeName := modelName + "." + jsonName
|
||||
prop.Ref = &nestedTypeName
|
||||
b.addModel(fieldType, nestedTypeName)
|
||||
}
|
||||
return jsonName, modelDescription, prop
|
||||
}
|
||||
|
||||
func hasNamedJSONTag(field reflect.StructField) bool {
|
||||
parts := strings.Split(field.Tag.Get("json"), ",")
|
||||
if len(parts) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, s := range parts[1:] {
|
||||
if s == "inline" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(parts[0]) > 0
|
||||
}
|
||||
|
||||
func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonName string, model *Model) (nameJson string, prop ModelProperty) {
|
||||
prop.setPropertyMetadata(field)
|
||||
// Check for type override in tag
|
||||
if prop.Type != nil {
|
||||
return jsonName, prop
|
||||
}
|
||||
fieldType := field.Type
|
||||
// check for anonymous
|
||||
if len(fieldType.Name()) == 0 {
|
||||
// anonymous
|
||||
anonType := model.Id + "." + jsonName
|
||||
b.addModel(fieldType, anonType)
|
||||
prop.Ref = &anonType
|
||||
return jsonName, prop
|
||||
}
|
||||
|
||||
if field.Name == fieldType.Name() && field.Anonymous && !hasNamedJSONTag(field) {
|
||||
// embedded struct
|
||||
sub := modelBuilder{new(ModelList), b.Config}
|
||||
sub.addModel(fieldType, "")
|
||||
subKey := sub.keyFrom(fieldType)
|
||||
// merge properties from sub
|
||||
subModel, _ := sub.Models.At(subKey)
|
||||
subModel.Properties.Do(func(k string, v ModelProperty) {
|
||||
model.Properties.Put(k, v)
|
||||
// if subModel says this property is required then include it
|
||||
required := false
|
||||
for _, each := range subModel.Required {
|
||||
if k == each {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if required {
|
||||
model.Required = append(model.Required, k)
|
||||
}
|
||||
})
|
||||
// add all new referenced models
|
||||
sub.Models.Do(func(key string, sub Model) {
|
||||
if key != subKey {
|
||||
if _, ok := b.Models.At(key); !ok {
|
||||
b.Models.Put(key, sub)
|
||||
}
|
||||
}
|
||||
})
|
||||
// empty name signals skip property
|
||||
return "", prop
|
||||
}
|
||||
// simple struct
|
||||
b.addModel(fieldType, "")
|
||||
var pType = b.keyFrom(fieldType)
|
||||
prop.Ref = &pType
|
||||
return jsonName, prop
|
||||
}
|
||||
|
||||
func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
|
||||
// check for type override in tags
|
||||
prop.setPropertyMetadata(field)
|
||||
if prop.Type != nil {
|
||||
return jsonName, prop
|
||||
}
|
||||
fieldType := field.Type
|
||||
if fieldType.Elem().Kind() == reflect.Uint8 {
|
||||
stringt := "string"
|
||||
prop.Type = &stringt
|
||||
return jsonName, prop
|
||||
}
|
||||
var pType = "array"
|
||||
prop.Type = &pType
|
||||
isPrimitive := b.isPrimitiveType(fieldType.Elem().Name())
|
||||
elemTypeName := b.getElementTypeName(modelName, jsonName, fieldType.Elem())
|
||||
prop.Items = new(Item)
|
||||
if isPrimitive {
|
||||
mapped := b.jsonSchemaType(elemTypeName)
|
||||
prop.Items.Type = &mapped
|
||||
} else {
|
||||
prop.Items.Ref = &elemTypeName
|
||||
}
|
||||
// add|overwrite model for element type
|
||||
if fieldType.Elem().Kind() == reflect.Ptr {
|
||||
fieldType = fieldType.Elem()
|
||||
}
|
||||
if !isPrimitive {
|
||||
b.addModel(fieldType.Elem(), elemTypeName)
|
||||
}
|
||||
return jsonName, prop
|
||||
}
|
||||
|
||||
func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
|
||||
prop.setPropertyMetadata(field)
|
||||
// Check for type override in tags
|
||||
if prop.Type != nil {
|
||||
return jsonName, prop
|
||||
}
|
||||
fieldType := field.Type
|
||||
|
||||
// override type of pointer to list-likes
|
||||
if fieldType.Elem().Kind() == reflect.Slice || fieldType.Elem().Kind() == reflect.Array {
|
||||
var pType = "array"
|
||||
prop.Type = &pType
|
||||
isPrimitive := b.isPrimitiveType(fieldType.Elem().Elem().Name())
|
||||
elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem().Elem())
|
||||
if isPrimitive {
|
||||
primName := b.jsonSchemaType(elemName)
|
||||
prop.Items = &Item{Ref: &primName}
|
||||
} else {
|
||||
prop.Items = &Item{Ref: &elemName}
|
||||
}
|
||||
if !isPrimitive {
|
||||
// add|overwrite model for element type
|
||||
b.addModel(fieldType.Elem().Elem(), elemName)
|
||||
}
|
||||
} else {
|
||||
// non-array, pointer type
|
||||
fieldTypeName := b.keyFrom(fieldType.Elem())
|
||||
var pType = b.jsonSchemaType(fieldTypeName) // no star, include pkg path
|
||||
if b.isPrimitiveType(fieldTypeName) {
|
||||
prop.Type = &pType
|
||||
prop.Format = b.jsonSchemaFormat(fieldTypeName)
|
||||
return jsonName, prop
|
||||
}
|
||||
prop.Ref = &pType
|
||||
elemName := ""
|
||||
if fieldType.Elem().Name() == "" {
|
||||
elemName = modelName + "." + jsonName
|
||||
prop.Ref = &elemName
|
||||
}
|
||||
b.addModel(fieldType.Elem(), elemName)
|
||||
}
|
||||
return jsonName, prop
|
||||
}
|
||||
|
||||
func (b modelBuilder) getElementTypeName(modelName, jsonName string, t reflect.Type) string {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Name() == "" {
|
||||
return modelName + "." + jsonName
|
||||
}
|
||||
return b.keyFrom(t)
|
||||
}
|
||||
|
||||
func (b modelBuilder) keyFrom(st reflect.Type) string {
|
||||
key := st.String()
|
||||
if b.Config != nil && b.Config.ModelTypeNameHandler != nil {
|
||||
if name, ok := b.Config.ModelTypeNameHandler(st); ok {
|
||||
key = name
|
||||
}
|
||||
}
|
||||
if len(st.Name()) == 0 { // unnamed type
|
||||
// Swagger UI has special meaning for [
|
||||
key = strings.Replace(key, "[]", "||", -1)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// see also https://golang.org/ref/spec#Numeric_types
|
||||
func (b modelBuilder) isPrimitiveType(modelName string) bool {
|
||||
if len(modelName) == 0 {
|
||||
return false
|
||||
}
|
||||
return strings.Contains("uint uint8 uint16 uint32 uint64 int int8 int16 int32 int64 float32 float64 bool string byte rune time.Time", modelName)
|
||||
}
|
||||
|
||||
// jsonNameOfField returns the name of the field as it should appear in JSON format
|
||||
// An empty string indicates that this field is not part of the JSON representation
|
||||
func (b modelBuilder) jsonNameOfField(field reflect.StructField) string {
|
||||
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
|
||||
s := strings.Split(jsonTag, ",")
|
||||
if s[0] == "-" {
|
||||
// empty name signals skip property
|
||||
return ""
|
||||
} else if s[0] != "" {
|
||||
return s[0]
|
||||
}
|
||||
}
|
||||
return field.Name
|
||||
}
|
||||
|
||||
// see also http://json-schema.org/latest/json-schema-core.html#anchor8
|
||||
func (b modelBuilder) jsonSchemaType(modelName string) string {
|
||||
schemaMap := map[string]string{
|
||||
"uint": "integer",
|
||||
"uint8": "integer",
|
||||
"uint16": "integer",
|
||||
"uint32": "integer",
|
||||
"uint64": "integer",
|
||||
|
||||
"int": "integer",
|
||||
"int8": "integer",
|
||||
"int16": "integer",
|
||||
"int32": "integer",
|
||||
"int64": "integer",
|
||||
|
||||
"byte": "integer",
|
||||
"float64": "number",
|
||||
"float32": "number",
|
||||
"bool": "boolean",
|
||||
"time.Time": "string",
|
||||
}
|
||||
mapped, ok := schemaMap[modelName]
|
||||
if !ok {
|
||||
return modelName // use as is (custom or struct)
|
||||
}
|
||||
return mapped
|
||||
}
|
||||
|
||||
func (b modelBuilder) jsonSchemaFormat(modelName string) string {
|
||||
if b.Config != nil && b.Config.SchemaFormatHandler != nil {
|
||||
if mapped := b.Config.SchemaFormatHandler(modelName); mapped != "" {
|
||||
return mapped
|
||||
}
|
||||
}
|
||||
schemaMap := map[string]string{
|
||||
"int": "int32",
|
||||
"int32": "int32",
|
||||
"int64": "int64",
|
||||
"byte": "byte",
|
||||
"uint": "integer",
|
||||
"uint8": "byte",
|
||||
"float64": "double",
|
||||
"float32": "float",
|
||||
"time.Time": "date-time",
|
||||
"*time.Time": "date-time",
|
||||
}
|
||||
mapped, ok := schemaMap[modelName]
|
||||
if !ok {
|
||||
return "" // no format
|
||||
}
|
||||
return mapped
|
||||
}
|
||||
1283
vendor/github.com/emicklei/go-restful-swagger12/model_builder_test.go
generated
vendored
1283
vendor/github.com/emicklei/go-restful-swagger12/model_builder_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
86
vendor/github.com/emicklei/go-restful-swagger12/model_list.go
generated
vendored
86
vendor/github.com/emicklei/go-restful-swagger12/model_list.go
generated
vendored
@@ -1,86 +0,0 @@
|
||||
package swagger
|
||||
|
||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// NamedModel associates a name with a Model (not using its Id)
|
||||
type NamedModel struct {
|
||||
Name string
|
||||
Model Model
|
||||
}
|
||||
|
||||
// ModelList encapsulates a list of NamedModel (association)
|
||||
type ModelList struct {
|
||||
List []NamedModel
|
||||
}
|
||||
|
||||
// Put adds or replaces a Model by its name
|
||||
func (l *ModelList) Put(name string, model Model) {
|
||||
for i, each := range l.List {
|
||||
if each.Name == name {
|
||||
// replace
|
||||
l.List[i] = NamedModel{name, model}
|
||||
return
|
||||
}
|
||||
}
|
||||
// add
|
||||
l.List = append(l.List, NamedModel{name, model})
|
||||
}
|
||||
|
||||
// At returns a Model by its name, ok is false if absent
|
||||
func (l *ModelList) At(name string) (m Model, ok bool) {
|
||||
for _, each := range l.List {
|
||||
if each.Name == name {
|
||||
return each.Model, true
|
||||
}
|
||||
}
|
||||
return m, false
|
||||
}
|
||||
|
||||
// Do enumerates all the models, each with its assigned name
|
||||
func (l *ModelList) Do(block func(name string, value Model)) {
|
||||
for _, each := range l.List {
|
||||
block(each.Name, each.Model)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON writes the ModelList as if it was a map[string]Model
|
||||
func (l ModelList) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
encoder := json.NewEncoder(&buf)
|
||||
buf.WriteString("{\n")
|
||||
for i, each := range l.List {
|
||||
buf.WriteString("\"")
|
||||
buf.WriteString(each.Name)
|
||||
buf.WriteString("\": ")
|
||||
encoder.Encode(each.Model)
|
||||
if i < len(l.List)-1 {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
buf.WriteString("}")
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON reads back a ModelList. This is an expensive operation.
|
||||
func (l *ModelList) UnmarshalJSON(data []byte) error {
|
||||
raw := map[string]interface{}{}
|
||||
json.NewDecoder(bytes.NewReader(data)).Decode(&raw)
|
||||
for k, v := range raw {
|
||||
// produces JSON bytes for each value
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var m Model
|
||||
json.NewDecoder(bytes.NewReader(data)).Decode(&m)
|
||||
l.Put(k, m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
48
vendor/github.com/emicklei/go-restful-swagger12/model_list_test.go
generated
vendored
48
vendor/github.com/emicklei/go-restful-swagger12/model_list_test.go
generated
vendored
@@ -1,48 +0,0 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestModelList(t *testing.T) {
|
||||
m := Model{}
|
||||
m.Id = "m"
|
||||
l := ModelList{}
|
||||
l.Put("m", m)
|
||||
k, ok := l.At("m")
|
||||
if !ok {
|
||||
t.Error("want model back")
|
||||
}
|
||||
if got, want := k.Id, "m"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelList_Marshal(t *testing.T) {
|
||||
l := ModelList{}
|
||||
m := Model{Id: "myid"}
|
||||
l.Put("myid", m)
|
||||
data, err := json.Marshal(l)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if got, want := string(data), `{"myid":{"id":"myid","properties":{}}}`; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelList_Unmarshal(t *testing.T) {
|
||||
data := `{"myid":{"id":"myid","properties":{}}}`
|
||||
l := ModelList{}
|
||||
if err := json.Unmarshal([]byte(data), &l); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
m, ok := l.At("myid")
|
||||
if !ok {
|
||||
t.Error("expected myid")
|
||||
}
|
||||
if got, want := m.Id, "myid"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
81
vendor/github.com/emicklei/go-restful-swagger12/model_property_ext.go
generated
vendored
81
vendor/github.com/emicklei/go-restful-swagger12/model_property_ext.go
generated
vendored
@@ -1,81 +0,0 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (prop *ModelProperty) setDescription(field reflect.StructField) {
|
||||
if tag := field.Tag.Get("description"); tag != "" {
|
||||
prop.Description = tag
|
||||
}
|
||||
}
|
||||
|
||||
func (prop *ModelProperty) setDefaultValue(field reflect.StructField) {
|
||||
if tag := field.Tag.Get("default"); tag != "" {
|
||||
prop.DefaultValue = Special(tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (prop *ModelProperty) setEnumValues(field reflect.StructField) {
|
||||
// We use | to separate the enum values. This value is chosen
|
||||
// since its unlikely to be useful in actual enumeration values.
|
||||
if tag := field.Tag.Get("enum"); tag != "" {
|
||||
prop.Enum = strings.Split(tag, "|")
|
||||
}
|
||||
}
|
||||
|
||||
func (prop *ModelProperty) setMaximum(field reflect.StructField) {
|
||||
if tag := field.Tag.Get("maximum"); tag != "" {
|
||||
prop.Maximum = tag
|
||||
}
|
||||
}
|
||||
|
||||
func (prop *ModelProperty) setType(field reflect.StructField) {
|
||||
if tag := field.Tag.Get("type"); tag != "" {
|
||||
// Check if the first two characters of the type tag are
|
||||
// intended to emulate slice/array behaviour.
|
||||
//
|
||||
// If type is intended to be a slice/array then add the
|
||||
// overriden type to the array item instead of the main property
|
||||
if len(tag) > 2 && tag[0:2] == "[]" {
|
||||
pType := "array"
|
||||
prop.Type = &pType
|
||||
prop.Items = new(Item)
|
||||
|
||||
iType := tag[2:]
|
||||
prop.Items.Type = &iType
|
||||
return
|
||||
}
|
||||
|
||||
prop.Type = &tag
|
||||
}
|
||||
}
|
||||
|
||||
func (prop *ModelProperty) setMinimum(field reflect.StructField) {
|
||||
if tag := field.Tag.Get("minimum"); tag != "" {
|
||||
prop.Minimum = tag
|
||||
}
|
||||
}
|
||||
|
||||
func (prop *ModelProperty) setUniqueItems(field reflect.StructField) {
|
||||
tag := field.Tag.Get("unique")
|
||||
switch tag {
|
||||
case "true":
|
||||
v := true
|
||||
prop.UniqueItems = &v
|
||||
case "false":
|
||||
v := false
|
||||
prop.UniqueItems = &v
|
||||
}
|
||||
}
|
||||
|
||||
func (prop *ModelProperty) setPropertyMetadata(field reflect.StructField) {
|
||||
prop.setDescription(field)
|
||||
prop.setEnumValues(field)
|
||||
prop.setMinimum(field)
|
||||
prop.setMaximum(field)
|
||||
prop.setUniqueItems(field)
|
||||
prop.setDefaultValue(field)
|
||||
prop.setType(field)
|
||||
}
|
||||
70
vendor/github.com/emicklei/go-restful-swagger12/model_property_ext_test.go
generated
vendored
70
vendor/github.com/emicklei/go-restful-swagger12/model_property_ext_test.go
generated
vendored
@@ -1,70 +0,0 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// clear && go test -v -test.run TestThatExtraTagsAreReadIntoModel ...swagger
|
||||
func TestThatExtraTagsAreReadIntoModel(t *testing.T) {
|
||||
type fakeint int
|
||||
type fakearray string
|
||||
type Anything struct {
|
||||
Name string `description:"name" modelDescription:"a test"`
|
||||
Size int `minimum:"0" maximum:"10"`
|
||||
Stati string `enum:"off|on" default:"on" modelDescription:"more description"`
|
||||
ID string `unique:"true"`
|
||||
FakeInt fakeint `type:"integer"`
|
||||
FakeArray fakearray `type:"[]string"`
|
||||
IP net.IP `type:"string"`
|
||||
Password string
|
||||
}
|
||||
m := modelsFromStruct(Anything{})
|
||||
props, _ := m.At("swagger.Anything")
|
||||
p1, _ := props.Properties.At("Name")
|
||||
if got, want := p1.Description, "name"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
p2, _ := props.Properties.At("Size")
|
||||
if got, want := p2.Minimum, "0"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
if got, want := p2.Maximum, "10"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
p3, _ := props.Properties.At("Stati")
|
||||
if got, want := p3.Enum[0], "off"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
if got, want := p3.Enum[1], "on"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
p4, _ := props.Properties.At("ID")
|
||||
if got, want := *p4.UniqueItems, true; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
p5, _ := props.Properties.At("Password")
|
||||
if got, want := *p5.Type, "string"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
p6, _ := props.Properties.At("FakeInt")
|
||||
if got, want := *p6.Type, "integer"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
p7, _ := props.Properties.At("FakeArray")
|
||||
if got, want := *p7.Type, "array"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
p7p, _ := props.Properties.At("FakeArray")
|
||||
if got, want := *p7p.Items.Type, "string"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
p8, _ := props.Properties.At("IP")
|
||||
if got, want := *p8.Type, "string"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
|
||||
if got, want := props.Description, "a test\nmore description"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
87
vendor/github.com/emicklei/go-restful-swagger12/model_property_list.go
generated
vendored
87
vendor/github.com/emicklei/go-restful-swagger12/model_property_list.go
generated
vendored
@@ -1,87 +0,0 @@
|
||||
package swagger
|
||||
|
||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// NamedModelProperty associates a name to a ModelProperty
|
||||
type NamedModelProperty struct {
|
||||
Name string
|
||||
Property ModelProperty
|
||||
}
|
||||
|
||||
// ModelPropertyList encapsulates a list of NamedModelProperty (association)
|
||||
type ModelPropertyList struct {
|
||||
List []NamedModelProperty
|
||||
}
|
||||
|
||||
// At returns the ModelPropety by its name unless absent, then ok is false
|
||||
func (l *ModelPropertyList) At(name string) (p ModelProperty, ok bool) {
|
||||
for _, each := range l.List {
|
||||
if each.Name == name {
|
||||
return each.Property, true
|
||||
}
|
||||
}
|
||||
return p, false
|
||||
}
|
||||
|
||||
// Put adds or replaces a ModelProperty with this name
|
||||
func (l *ModelPropertyList) Put(name string, prop ModelProperty) {
|
||||
// maybe replace existing
|
||||
for i, each := range l.List {
|
||||
if each.Name == name {
|
||||
// replace
|
||||
l.List[i] = NamedModelProperty{Name: name, Property: prop}
|
||||
return
|
||||
}
|
||||
}
|
||||
// add
|
||||
l.List = append(l.List, NamedModelProperty{Name: name, Property: prop})
|
||||
}
|
||||
|
||||
// Do enumerates all the properties, each with its assigned name
|
||||
func (l *ModelPropertyList) Do(block func(name string, value ModelProperty)) {
|
||||
for _, each := range l.List {
|
||||
block(each.Name, each.Property)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON writes the ModelPropertyList as if it was a map[string]ModelProperty
|
||||
func (l ModelPropertyList) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
encoder := json.NewEncoder(&buf)
|
||||
buf.WriteString("{\n")
|
||||
for i, each := range l.List {
|
||||
buf.WriteString("\"")
|
||||
buf.WriteString(each.Name)
|
||||
buf.WriteString("\": ")
|
||||
encoder.Encode(each.Property)
|
||||
if i < len(l.List)-1 {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
buf.WriteString("}")
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON reads back a ModelPropertyList. This is an expensive operation.
|
||||
func (l *ModelPropertyList) UnmarshalJSON(data []byte) error {
|
||||
raw := map[string]interface{}{}
|
||||
json.NewDecoder(bytes.NewReader(data)).Decode(&raw)
|
||||
for k, v := range raw {
|
||||
// produces JSON bytes for each value
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var m ModelProperty
|
||||
json.NewDecoder(bytes.NewReader(data)).Decode(&m)
|
||||
l.Put(k, m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
47
vendor/github.com/emicklei/go-restful-swagger12/model_property_list_test.go
generated
vendored
47
vendor/github.com/emicklei/go-restful-swagger12/model_property_list_test.go
generated
vendored
@@ -1,47 +0,0 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestModelPropertyList(t *testing.T) {
|
||||
l := ModelPropertyList{}
|
||||
p := ModelProperty{Description: "d"}
|
||||
l.Put("p", p)
|
||||
q, ok := l.At("p")
|
||||
if !ok {
|
||||
t.Error("expected p")
|
||||
}
|
||||
if got, want := q.Description, "d"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelPropertyList_Marshal(t *testing.T) {
|
||||
l := ModelPropertyList{}
|
||||
p := ModelProperty{Description: "d"}
|
||||
l.Put("p", p)
|
||||
data, err := json.Marshal(l)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if got, want := string(data), `{"p":{"description":"d"}}`; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelPropertyList_Unmarshal(t *testing.T) {
|
||||
data := `{"p":{"description":"d"}}`
|
||||
l := ModelPropertyList{}
|
||||
if err := json.Unmarshal([]byte(data), &l); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
m, ok := l.At("p")
|
||||
if !ok {
|
||||
t.Error("expected p")
|
||||
}
|
||||
if got, want := m.Description, "d"; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
36
vendor/github.com/emicklei/go-restful-swagger12/ordered_route_map.go
generated
vendored
36
vendor/github.com/emicklei/go-restful-swagger12/ordered_route_map.go
generated
vendored
@@ -1,36 +0,0 @@
|
||||
package swagger
|
||||
|
||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import "github.com/emicklei/go-restful"
|
||||
|
||||
type orderedRouteMap struct {
|
||||
elements map[string][]restful.Route
|
||||
keys []string
|
||||
}
|
||||
|
||||
func newOrderedRouteMap() *orderedRouteMap {
|
||||
return &orderedRouteMap{
|
||||
elements: map[string][]restful.Route{},
|
||||
keys: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *orderedRouteMap) Add(key string, route restful.Route) {
|
||||
routes, ok := o.elements[key]
|
||||
if ok {
|
||||
routes = append(routes, route)
|
||||
o.elements[key] = routes
|
||||
return
|
||||
}
|
||||
o.elements[key] = []restful.Route{route}
|
||||
o.keys = append(o.keys, key)
|
||||
}
|
||||
|
||||
func (o *orderedRouteMap) Do(block func(key string, routes []restful.Route)) {
|
||||
for _, k := range o.keys {
|
||||
block(k, o.elements[k])
|
||||
}
|
||||
}
|
||||
29
vendor/github.com/emicklei/go-restful-swagger12/ordered_route_map_test.go
generated
vendored
29
vendor/github.com/emicklei/go-restful-swagger12/ordered_route_map_test.go
generated
vendored
@@ -1,29 +0,0 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
)
|
||||
|
||||
// go test -v -test.run TestOrderedRouteMap ...swagger
|
||||
func TestOrderedRouteMap(t *testing.T) {
|
||||
m := newOrderedRouteMap()
|
||||
r1 := restful.Route{Path: "/r1"}
|
||||
r2 := restful.Route{Path: "/r2"}
|
||||
m.Add("a", r1)
|
||||
m.Add("b", r2)
|
||||
m.Add("b", r1)
|
||||
m.Add("d", r2)
|
||||
m.Add("c", r2)
|
||||
order := ""
|
||||
m.Do(func(k string, routes []restful.Route) {
|
||||
order += k
|
||||
if len(routes) == 0 {
|
||||
t.Fail()
|
||||
}
|
||||
})
|
||||
if order != "abdc" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
42
vendor/github.com/emicklei/go-restful-swagger12/postbuild_model_test.go
generated
vendored
42
vendor/github.com/emicklei/go-restful-swagger12/postbuild_model_test.go
generated
vendored
@@ -1,42 +0,0 @@
|
||||
package swagger
|
||||
|
||||
import "testing"
|
||||
|
||||
type Boat struct {
|
||||
Length int `json:"-"` // on default, this makes the fields not required
|
||||
Weight int `json:"-"`
|
||||
}
|
||||
|
||||
// PostBuildModel is from swagger.ModelBuildable
|
||||
func (b Boat) PostBuildModel(m *Model) *Model {
|
||||
// override required
|
||||
m.Required = []string{"Length", "Weight"}
|
||||
|
||||
// add model property (just to test is can be added; is this a real usecase?)
|
||||
extraType := "string"
|
||||
m.Properties.Put("extra", ModelProperty{
|
||||
Description: "extra description",
|
||||
DataTypeFields: DataTypeFields{
|
||||
Type: &extraType,
|
||||
},
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
func TestCustomPostModelBuilde(t *testing.T) {
|
||||
testJsonFromStruct(t, Boat{}, `{
|
||||
"swagger.Boat": {
|
||||
"id": "swagger.Boat",
|
||||
"required": [
|
||||
"Length",
|
||||
"Weight"
|
||||
],
|
||||
"properties": {
|
||||
"extra": {
|
||||
"type": "string",
|
||||
"description": "extra description"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
}
|
||||
185
vendor/github.com/emicklei/go-restful-swagger12/swagger.go
generated
vendored
185
vendor/github.com/emicklei/go-restful-swagger12/swagger.go
generated
vendored
@@ -1,185 +0,0 @@
|
||||
// Package swagger implements the structures of the Swagger
|
||||
// https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md
|
||||
package swagger
|
||||
|
||||
const swaggerVersion = "1.2"
|
||||
|
||||
// 4.3.3 Data Type Fields
|
||||
type DataTypeFields struct {
|
||||
Type *string `json:"type,omitempty"` // if Ref not used
|
||||
Ref *string `json:"$ref,omitempty"` // if Type not used
|
||||
Format string `json:"format,omitempty"`
|
||||
DefaultValue Special `json:"defaultValue,omitempty"`
|
||||
Enum []string `json:"enum,omitempty"`
|
||||
Minimum string `json:"minimum,omitempty"`
|
||||
Maximum string `json:"maximum,omitempty"`
|
||||
Items *Item `json:"items,omitempty"`
|
||||
UniqueItems *bool `json:"uniqueItems,omitempty"`
|
||||
}
|
||||
|
||||
type Special string
|
||||
|
||||
// 4.3.4 Items Object
|
||||
type Item struct {
|
||||
Type *string `json:"type,omitempty"`
|
||||
Ref *string `json:"$ref,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
}
|
||||
|
||||
// 5.1 Resource Listing
|
||||
type ResourceListing struct {
|
||||
SwaggerVersion string `json:"swaggerVersion"` // e.g 1.2
|
||||
Apis []Resource `json:"apis"`
|
||||
ApiVersion string `json:"apiVersion"`
|
||||
Info Info `json:"info"`
|
||||
Authorizations []Authorization `json:"authorizations,omitempty"`
|
||||
}
|
||||
|
||||
// 5.1.2 Resource Object
|
||||
type Resource struct {
|
||||
Path string `json:"path"` // relative or absolute, must start with /
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// 5.1.3 Info Object
|
||||
type Info struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
TermsOfServiceUrl string `json:"termsOfServiceUrl,omitempty"`
|
||||
Contact string `json:"contact,omitempty"`
|
||||
License string `json:"license,omitempty"`
|
||||
LicenseUrl string `json:"licenseUrl,omitempty"`
|
||||
}
|
||||
|
||||
// 5.1.5
|
||||
type Authorization struct {
|
||||
Type string `json:"type"`
|
||||
PassAs string `json:"passAs"`
|
||||
Keyname string `json:"keyname"`
|
||||
Scopes []Scope `json:"scopes"`
|
||||
GrantTypes []GrantType `json:"grandTypes"`
|
||||
}
|
||||
|
||||
// 5.1.6, 5.2.11
|
||||
type Scope struct {
|
||||
// Required. The name of the scope.
|
||||
Scope string `json:"scope"`
|
||||
// Recommended. A short description of the scope.
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// 5.1.7
|
||||
type GrantType struct {
|
||||
Implicit Implicit `json:"implicit"`
|
||||
AuthorizationCode AuthorizationCode `json:"authorization_code"`
|
||||
}
|
||||
|
||||
// 5.1.8 Implicit Object
|
||||
type Implicit struct {
|
||||
// Required. The login endpoint definition.
|
||||
loginEndpoint LoginEndpoint `json:"loginEndpoint"`
|
||||
// An optional alternative name to standard "access_token" OAuth2 parameter.
|
||||
TokenName string `json:"tokenName"`
|
||||
}
|
||||
|
||||
// 5.1.9 Authorization Code Object
|
||||
type AuthorizationCode struct {
|
||||
TokenRequestEndpoint TokenRequestEndpoint `json:"tokenRequestEndpoint"`
|
||||
TokenEndpoint TokenEndpoint `json:"tokenEndpoint"`
|
||||
}
|
||||
|
||||
// 5.1.10 Login Endpoint Object
|
||||
type LoginEndpoint struct {
|
||||
// Required. The URL of the authorization endpoint for the implicit grant flow. The value SHOULD be in a URL format.
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
// 5.1.11 Token Request Endpoint Object
|
||||
type TokenRequestEndpoint struct {
|
||||
// Required. The URL of the authorization endpoint for the authentication code grant flow. The value SHOULD be in a URL format.
|
||||
Url string `json:"url"`
|
||||
// An optional alternative name to standard "client_id" OAuth2 parameter.
|
||||
ClientIdName string `json:"clientIdName"`
|
||||
// An optional alternative name to the standard "client_secret" OAuth2 parameter.
|
||||
ClientSecretName string `json:"clientSecretName"`
|
||||
}
|
||||
|
||||
// 5.1.12 Token Endpoint Object
|
||||
type TokenEndpoint struct {
|
||||
// Required. The URL of the token endpoint for the authentication code grant flow. The value SHOULD be in a URL format.
|
||||
Url string `json:"url"`
|
||||
// An optional alternative name to standard "access_token" OAuth2 parameter.
|
||||
TokenName string `json:"tokenName"`
|
||||
}
|
||||
|
||||
// 5.2 API Declaration
|
||||
type ApiDeclaration struct {
|
||||
SwaggerVersion string `json:"swaggerVersion"`
|
||||
ApiVersion string `json:"apiVersion"`
|
||||
BasePath string `json:"basePath"`
|
||||
ResourcePath string `json:"resourcePath"` // must start with /
|
||||
Info Info `json:"info"`
|
||||
Apis []Api `json:"apis,omitempty"`
|
||||
Models ModelList `json:"models,omitempty"`
|
||||
Produces []string `json:"produces,omitempty"`
|
||||
Consumes []string `json:"consumes,omitempty"`
|
||||
Authorizations []Authorization `json:"authorizations,omitempty"`
|
||||
}
|
||||
|
||||
// 5.2.2 API Object
|
||||
type Api struct {
|
||||
Path string `json:"path"` // relative or absolute, must start with /
|
||||
Description string `json:"description"`
|
||||
Operations []Operation `json:"operations,omitempty"`
|
||||
}
|
||||
|
||||
// 5.2.3 Operation Object
|
||||
type Operation struct {
|
||||
DataTypeFields
|
||||
Method string `json:"method"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
Nickname string `json:"nickname"`
|
||||
Authorizations []Authorization `json:"authorizations,omitempty"`
|
||||
Parameters []Parameter `json:"parameters"`
|
||||
ResponseMessages []ResponseMessage `json:"responseMessages,omitempty"` // optional
|
||||
Produces []string `json:"produces,omitempty"`
|
||||
Consumes []string `json:"consumes,omitempty"`
|
||||
Deprecated string `json:"deprecated,omitempty"`
|
||||
}
|
||||
|
||||
// 5.2.4 Parameter Object
|
||||
type Parameter struct {
|
||||
DataTypeFields
|
||||
ParamType string `json:"paramType"` // path,query,body,header,form
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Required bool `json:"required"`
|
||||
AllowMultiple bool `json:"allowMultiple"`
|
||||
}
|
||||
|
||||
// 5.2.5 Response Message Object
|
||||
type ResponseMessage struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
ResponseModel string `json:"responseModel,omitempty"`
|
||||
}
|
||||
|
||||
// 5.2.6, 5.2.7 Models Object
|
||||
type Model struct {
|
||||
Id string `json:"id"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Required []string `json:"required,omitempty"`
|
||||
Properties ModelPropertyList `json:"properties"`
|
||||
SubTypes []string `json:"subTypes,omitempty"`
|
||||
Discriminator string `json:"discriminator,omitempty"`
|
||||
}
|
||||
|
||||
// 5.2.8 Properties Object
|
||||
type ModelProperty struct {
|
||||
DataTypeFields
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// 5.2.10
|
||||
type Authorizations map[string]Authorization
|
||||
21
vendor/github.com/emicklei/go-restful-swagger12/swagger_builder.go
generated
vendored
21
vendor/github.com/emicklei/go-restful-swagger12/swagger_builder.go
generated
vendored
@@ -1,21 +0,0 @@
|
||||
package swagger
|
||||
|
||||
type SwaggerBuilder struct {
|
||||
SwaggerService
|
||||
}
|
||||
|
||||
func NewSwaggerBuilder(config Config) *SwaggerBuilder {
|
||||
return &SwaggerBuilder{*newSwaggerService(config)}
|
||||
}
|
||||
|
||||
func (sb SwaggerBuilder) ProduceListing() ResourceListing {
|
||||
return sb.SwaggerService.produceListing()
|
||||
}
|
||||
|
||||
func (sb SwaggerBuilder) ProduceAllDeclarations() map[string]ApiDeclaration {
|
||||
return sb.SwaggerService.produceAllDeclarations()
|
||||
}
|
||||
|
||||
func (sb SwaggerBuilder) ProduceDeclarations(route string) (*ApiDeclaration, bool) {
|
||||
return sb.SwaggerService.produceDeclarations(route)
|
||||
}
|
||||
318
vendor/github.com/emicklei/go-restful-swagger12/swagger_test.go
generated
vendored
318
vendor/github.com/emicklei/go-restful-swagger12/swagger_test.go
generated
vendored
@@ -1,318 +0,0 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/emicklei/go-restful-swagger12/test_package"
|
||||
)
|
||||
|
||||
func TestInfoStruct_Issue231(t *testing.T) {
|
||||
config := Config{
|
||||
Info: Info{
|
||||
Title: "Title",
|
||||
Description: "Description",
|
||||
TermsOfServiceUrl: "http://example.com",
|
||||
Contact: "example@example.com",
|
||||
License: "License",
|
||||
LicenseUrl: "http://example.com/license.txt",
|
||||
},
|
||||
}
|
||||
sws := newSwaggerService(config)
|
||||
str, err := json.MarshalIndent(sws.produceListing(), "", " ")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
compareJson(t, string(str), `
|
||||
{
|
||||
"apiVersion": "",
|
||||
"swaggerVersion": "1.2",
|
||||
"apis": null,
|
||||
"info": {
|
||||
"title": "Title",
|
||||
"description": "Description",
|
||||
"termsOfServiceUrl": "http://example.com",
|
||||
"contact": "example@example.com",
|
||||
"license": "License",
|
||||
"licenseUrl": "http://example.com/license.txt"
|
||||
}
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
// go test -v -test.run TestThatMultiplePathsOnRootAreHandled ...swagger
|
||||
func TestThatMultiplePathsOnRootAreHandled(t *testing.T) {
|
||||
ws1 := new(restful.WebService)
|
||||
ws1.Route(ws1.GET("/_ping").To(dummy))
|
||||
ws1.Route(ws1.GET("/version").To(dummy))
|
||||
|
||||
cfg := Config{
|
||||
WebServicesUrl: "http://here.com",
|
||||
ApiPath: "/apipath",
|
||||
WebServices: []*restful.WebService{ws1},
|
||||
}
|
||||
sws := newSwaggerService(cfg)
|
||||
decl := sws.composeDeclaration(ws1, "/")
|
||||
if got, want := len(decl.Apis), 2; got != want {
|
||||
t.Errorf("got %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteSamples(t *testing.T) {
|
||||
ws1 := new(restful.WebService)
|
||||
ws1.Route(ws1.GET("/object").To(dummy).Writes(test_package.TestStruct{}))
|
||||
ws1.Route(ws1.GET("/array").To(dummy).Writes([]test_package.TestStruct{}))
|
||||
ws1.Route(ws1.GET("/object_and_array").To(dummy).Writes(struct{ Abc test_package.TestStruct }{}))
|
||||
|
||||
cfg := Config{
|
||||
WebServicesUrl: "http://here.com",
|
||||
ApiPath: "/apipath",
|
||||
WebServices: []*restful.WebService{ws1},
|
||||
}
|
||||
sws := newSwaggerService(cfg)
|
||||
|
||||
decl := sws.composeDeclaration(ws1, "/")
|
||||
|
||||
str, err := json.MarshalIndent(decl.Apis, "", " ")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
compareJson(t, string(str), `
|
||||
[
|
||||
{
|
||||
"path": "/object",
|
||||
"description": "",
|
||||
"operations": [
|
||||
{
|
||||
"type": "test_package.TestStruct",
|
||||
"method": "GET",
|
||||
"nickname": "dummy",
|
||||
"parameters": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/array",
|
||||
"description": "",
|
||||
"operations": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "test_package.TestStruct"
|
||||
},
|
||||
"method": "GET",
|
||||
"nickname": "dummy",
|
||||
"parameters": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/object_and_array",
|
||||
"description": "",
|
||||
"operations": [
|
||||
{
|
||||
"type": "struct { Abc test_package.TestStruct }",
|
||||
"method": "GET",
|
||||
"nickname": "dummy",
|
||||
"parameters": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]`)
|
||||
|
||||
str, err = json.MarshalIndent(decl.Models, "", " ")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
compareJson(t, string(str), `
|
||||
{
|
||||
"test_package.TestStruct": {
|
||||
"id": "test_package.TestStruct",
|
||||
"required": [
|
||||
"TestField"
|
||||
],
|
||||
"properties": {
|
||||
"TestField": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"||test_package.TestStruct": {
|
||||
"id": "||test_package.TestStruct",
|
||||
"properties": {}
|
||||
},
|
||||
"struct { Abc test_package.TestStruct }": {
|
||||
"id": "struct { Abc test_package.TestStruct }",
|
||||
"required": [
|
||||
"Abc"
|
||||
],
|
||||
"properties": {
|
||||
"Abc": {
|
||||
"$ref": "test_package.TestStruct"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
}
|
||||
|
||||
func TestRoutesWithCommonPart(t *testing.T) {
|
||||
ws1 := new(restful.WebService)
|
||||
ws1.Path("/")
|
||||
ws1.Route(ws1.GET("/foobar").To(dummy).Writes(test_package.TestStruct{}))
|
||||
ws1.Route(ws1.HEAD("/foobar").To(dummy).Writes(test_package.TestStruct{}))
|
||||
ws1.Route(ws1.GET("/foo").To(dummy).Writes([]test_package.TestStruct{}))
|
||||
ws1.Route(ws1.HEAD("/foo").To(dummy).Writes(test_package.TestStruct{}))
|
||||
|
||||
cfg := Config{
|
||||
WebServicesUrl: "http://here.com",
|
||||
ApiPath: "/apipath",
|
||||
WebServices: []*restful.WebService{ws1},
|
||||
}
|
||||
sws := newSwaggerService(cfg)
|
||||
|
||||
decl := sws.composeDeclaration(ws1, "/foo")
|
||||
|
||||
str, err := json.MarshalIndent(decl.Apis, "", " ")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
compareJson(t, string(str), `[
|
||||
{
|
||||
"path": "/foo",
|
||||
"description": "",
|
||||
"operations": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "test_package.TestStruct"
|
||||
},
|
||||
"method": "GET",
|
||||
"nickname": "dummy",
|
||||
"parameters": []
|
||||
},
|
||||
{
|
||||
"type": "test_package.TestStruct",
|
||||
"method": "HEAD",
|
||||
"nickname": "dummy",
|
||||
"parameters": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]`)
|
||||
}
|
||||
|
||||
// go test -v -test.run TestServiceToApi ...swagger
|
||||
func TestServiceToApi(t *testing.T) {
|
||||
ws := new(restful.WebService)
|
||||
ws.Path("/tests")
|
||||
ws.Consumes(restful.MIME_JSON)
|
||||
ws.Produces(restful.MIME_XML)
|
||||
ws.Route(ws.GET("/a").To(dummy).Writes(sample{}))
|
||||
ws.Route(ws.PUT("/b").To(dummy).Writes(sample{}))
|
||||
ws.Route(ws.POST("/c").To(dummy).Writes(sample{}))
|
||||
ws.Route(ws.DELETE("/d").To(dummy).Writes(sample{}))
|
||||
|
||||
ws.Route(ws.GET("/d").To(dummy).Writes(sample{}))
|
||||
ws.Route(ws.PUT("/c").To(dummy).Writes(sample{}))
|
||||
ws.Route(ws.POST("/b").To(dummy).Writes(sample{}))
|
||||
ws.Route(ws.DELETE("/a").To(dummy).Writes(sample{}))
|
||||
ws.ApiVersion("1.2.3")
|
||||
cfg := Config{
|
||||
WebServicesUrl: "http://here.com",
|
||||
ApiPath: "/apipath",
|
||||
WebServices: []*restful.WebService{ws},
|
||||
PostBuildHandler: func(in *ApiDeclarationList) {},
|
||||
}
|
||||
sws := newSwaggerService(cfg)
|
||||
decl := sws.composeDeclaration(ws, "/tests")
|
||||
// checks
|
||||
if decl.ApiVersion != "1.2.3" {
|
||||
t.Errorf("got %v want %v", decl.ApiVersion, "1.2.3")
|
||||
}
|
||||
if decl.BasePath != "http://here.com" {
|
||||
t.Errorf("got %v want %v", decl.BasePath, "http://here.com")
|
||||
}
|
||||
if len(decl.Apis) != 4 {
|
||||
t.Errorf("got %v want %v", len(decl.Apis), 4)
|
||||
}
|
||||
pathOrder := ""
|
||||
for _, each := range decl.Apis {
|
||||
pathOrder += each.Path
|
||||
for _, other := range each.Operations {
|
||||
pathOrder += other.Method
|
||||
}
|
||||
}
|
||||
|
||||
if pathOrder != "/tests/aGETDELETE/tests/bPUTPOST/tests/cPOSTPUT/tests/dDELETEGET" {
|
||||
t.Errorf("got %v want %v", pathOrder, "see test source")
|
||||
}
|
||||
}
|
||||
|
||||
func dummy(i *restful.Request, o *restful.Response) {}
|
||||
|
||||
// go test -v -test.run TestIssue78 ...swagger
|
||||
type Response struct {
|
||||
Code int
|
||||
Users *[]User
|
||||
Items *[]TestItem
|
||||
}
|
||||
type User struct {
|
||||
Id, Name string
|
||||
}
|
||||
type TestItem struct {
|
||||
Id, Name string
|
||||
}
|
||||
|
||||
// clear && go test -v -test.run TestComposeResponseMessages ...swagger
|
||||
func TestComposeResponseMessages(t *testing.T) {
|
||||
responseErrors := map[int]restful.ResponseError{}
|
||||
responseErrors[400] = restful.ResponseError{Code: 400, Message: "Bad Request", Model: TestItem{}}
|
||||
route := restful.Route{ResponseErrors: responseErrors}
|
||||
decl := new(ApiDeclaration)
|
||||
decl.Models = ModelList{}
|
||||
msgs := composeResponseMessages(route, decl, &Config{})
|
||||
if msgs[0].ResponseModel != "swagger.TestItem" {
|
||||
t.Errorf("got %s want swagger.TestItem", msgs[0].ResponseModel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue78(t *testing.T) {
|
||||
sws := newSwaggerService(Config{})
|
||||
models := new(ModelList)
|
||||
sws.addModelFromSampleTo(&Operation{}, true, Response{Items: &[]TestItem{}}, models)
|
||||
model, ok := models.At("swagger.Response")
|
||||
if !ok {
|
||||
t.Fatal("missing response model")
|
||||
}
|
||||
if "swagger.Response" != model.Id {
|
||||
t.Fatal("wrong model id:" + model.Id)
|
||||
}
|
||||
code, ok := model.Properties.At("Code")
|
||||
if !ok {
|
||||
t.Fatal("missing code")
|
||||
}
|
||||
if "integer" != *code.Type {
|
||||
t.Fatal("wrong code type:" + *code.Type)
|
||||
}
|
||||
items, ok := model.Properties.At("Items")
|
||||
if !ok {
|
||||
t.Fatal("missing items")
|
||||
}
|
||||
if "array" != *items.Type {
|
||||
t.Fatal("wrong items type:" + *items.Type)
|
||||
}
|
||||
items_items := items.Items
|
||||
if items_items == nil {
|
||||
t.Fatal("missing items->items")
|
||||
}
|
||||
ref := items_items.Ref
|
||||
if ref == nil {
|
||||
t.Fatal("missing $ref")
|
||||
}
|
||||
if *ref != "swagger.TestItem" {
|
||||
t.Fatal("wrong $ref:" + *ref)
|
||||
}
|
||||
}
|
||||
443
vendor/github.com/emicklei/go-restful-swagger12/swagger_webservice.go
generated
vendored
443
vendor/github.com/emicklei/go-restful-swagger12/swagger_webservice.go
generated
vendored
@@ -1,443 +0,0 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
// "github.com/emicklei/hopwatch"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/emicklei/go-restful/log"
|
||||
)
|
||||
|
||||
type SwaggerService struct {
|
||||
config Config
|
||||
apiDeclarationMap *ApiDeclarationList
|
||||
}
|
||||
|
||||
func newSwaggerService(config Config) *SwaggerService {
|
||||
sws := &SwaggerService{
|
||||
config: config,
|
||||
apiDeclarationMap: new(ApiDeclarationList)}
|
||||
|
||||
// Build all ApiDeclarations
|
||||
for _, each := range config.WebServices {
|
||||
rootPath := each.RootPath()
|
||||
// skip the api service itself
|
||||
if rootPath != config.ApiPath {
|
||||
if rootPath == "" || rootPath == "/" {
|
||||
// use routes
|
||||
for _, route := range each.Routes() {
|
||||
entry := staticPathFromRoute(route)
|
||||
_, exists := sws.apiDeclarationMap.At(entry)
|
||||
if !exists {
|
||||
sws.apiDeclarationMap.Put(entry, sws.composeDeclaration(each, entry))
|
||||
}
|
||||
}
|
||||
} else { // use root path
|
||||
sws.apiDeclarationMap.Put(each.RootPath(), sws.composeDeclaration(each, each.RootPath()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if specified then call the PostBuilderHandler
|
||||
if config.PostBuildHandler != nil {
|
||||
config.PostBuildHandler(sws.apiDeclarationMap)
|
||||
}
|
||||
return sws
|
||||
}
|
||||
|
||||
// LogInfo is the function that is called when this package needs to log. It defaults to log.Printf
|
||||
var LogInfo = func(format string, v ...interface{}) {
|
||||
// use the restful package-wide logger
|
||||
log.Printf(format, v...)
|
||||
}
|
||||
|
||||
// InstallSwaggerService add the WebService that provides the API documentation of all services
|
||||
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
|
||||
func InstallSwaggerService(aSwaggerConfig Config) {
|
||||
RegisterSwaggerService(aSwaggerConfig, restful.DefaultContainer)
|
||||
}
|
||||
|
||||
// RegisterSwaggerService add the WebService that provides the API documentation of all services
|
||||
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
|
||||
func RegisterSwaggerService(config Config, wsContainer *restful.Container) {
|
||||
sws := newSwaggerService(config)
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(config.ApiPath)
|
||||
ws.Produces(restful.MIME_JSON)
|
||||
if config.DisableCORS {
|
||||
ws.Filter(enableCORS)
|
||||
}
|
||||
ws.Route(ws.GET("/").To(sws.getListing))
|
||||
ws.Route(ws.GET("/{a}").To(sws.getDeclarations))
|
||||
ws.Route(ws.GET("/{a}/{b}").To(sws.getDeclarations))
|
||||
ws.Route(ws.GET("/{a}/{b}/{c}").To(sws.getDeclarations))
|
||||
ws.Route(ws.GET("/{a}/{b}/{c}/{d}").To(sws.getDeclarations))
|
||||
ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}").To(sws.getDeclarations))
|
||||
ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}").To(sws.getDeclarations))
|
||||
ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}/{g}").To(sws.getDeclarations))
|
||||
LogInfo("[restful/swagger] listing is available at %v%v", config.WebServicesUrl, config.ApiPath)
|
||||
wsContainer.Add(ws)
|
||||
|
||||
// Check paths for UI serving
|
||||
if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" {
|
||||
swaggerPathSlash := config.SwaggerPath
|
||||
// path must end with slash /
|
||||
if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] {
|
||||
LogInfo("[restful/swagger] use corrected SwaggerPath ; must end with slash (/)")
|
||||
swaggerPathSlash += "/"
|
||||
}
|
||||
|
||||
LogInfo("[restful/swagger] %v%v is mapped to folder %v", config.WebServicesUrl, swaggerPathSlash, config.SwaggerFilePath)
|
||||
wsContainer.Handle(swaggerPathSlash, http.StripPrefix(swaggerPathSlash, http.FileServer(http.Dir(config.SwaggerFilePath))))
|
||||
|
||||
//if we define a custom static handler use it
|
||||
} else if config.StaticHandler != nil && config.SwaggerPath != "" {
|
||||
swaggerPathSlash := config.SwaggerPath
|
||||
// path must end with slash /
|
||||
if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] {
|
||||
LogInfo("[restful/swagger] use corrected SwaggerFilePath ; must end with slash (/)")
|
||||
swaggerPathSlash += "/"
|
||||
|
||||
}
|
||||
LogInfo("[restful/swagger] %v%v is mapped to custom Handler %T", config.WebServicesUrl, swaggerPathSlash, config.StaticHandler)
|
||||
wsContainer.Handle(swaggerPathSlash, config.StaticHandler)
|
||||
|
||||
} else {
|
||||
LogInfo("[restful/swagger] Swagger(File)Path is empty ; no UI is served")
|
||||
}
|
||||
}
|
||||
|
||||
func staticPathFromRoute(r restful.Route) string {
|
||||
static := r.Path
|
||||
bracket := strings.Index(static, "{")
|
||||
if bracket <= 1 { // result cannot be empty
|
||||
return static
|
||||
}
|
||||
if bracket != -1 {
|
||||
static = r.Path[:bracket]
|
||||
}
|
||||
if strings.HasSuffix(static, "/") {
|
||||
return static[:len(static)-1]
|
||||
} else {
|
||||
return static
|
||||
}
|
||||
}
|
||||
|
||||
func enableCORS(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
||||
if origin := req.HeaderParameter(restful.HEADER_Origin); origin != "" {
|
||||
// prevent duplicate header
|
||||
if len(resp.Header().Get(restful.HEADER_AccessControlAllowOrigin)) == 0 {
|
||||
resp.AddHeader(restful.HEADER_AccessControlAllowOrigin, origin)
|
||||
}
|
||||
}
|
||||
chain.ProcessFilter(req, resp)
|
||||
}
|
||||
|
||||
func (sws SwaggerService) getListing(req *restful.Request, resp *restful.Response) {
|
||||
listing := sws.produceListing()
|
||||
resp.WriteAsJson(listing)
|
||||
}
|
||||
|
||||
func (sws SwaggerService) produceListing() ResourceListing {
|
||||
listing := ResourceListing{SwaggerVersion: swaggerVersion, ApiVersion: sws.config.ApiVersion, Info: sws.config.Info}
|
||||
sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
|
||||
ref := Resource{Path: k}
|
||||
if len(v.Apis) > 0 { // use description of first (could still be empty)
|
||||
ref.Description = v.Apis[0].Description
|
||||
}
|
||||
listing.Apis = append(listing.Apis, ref)
|
||||
})
|
||||
return listing
|
||||
}
|
||||
|
||||
func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Response) {
|
||||
decl, ok := sws.produceDeclarations(composeRootPath(req))
|
||||
if !ok {
|
||||
resp.WriteErrorString(http.StatusNotFound, "ApiDeclaration not found")
|
||||
return
|
||||
}
|
||||
// unless WebServicesUrl is given
|
||||
if len(sws.config.WebServicesUrl) == 0 {
|
||||
// update base path from the actual request
|
||||
// TODO how to detect https? assume http for now
|
||||
var host string
|
||||
// X-Forwarded-Host or Host or Request.Host
|
||||
hostvalues, ok := req.Request.Header["X-Forwarded-Host"] // apache specific?
|
||||
if !ok || len(hostvalues) == 0 {
|
||||
forwarded, ok := req.Request.Header["Host"] // without reverse-proxy
|
||||
if !ok || len(forwarded) == 0 {
|
||||
// fallback to Host field
|
||||
host = req.Request.Host
|
||||
} else {
|
||||
host = forwarded[0]
|
||||
}
|
||||
} else {
|
||||
host = hostvalues[0]
|
||||
}
|
||||
// inspect Referer for the scheme (http vs https)
|
||||
scheme := "http"
|
||||
if referer := req.Request.Header["Referer"]; len(referer) > 0 {
|
||||
if strings.HasPrefix(referer[0], "https") {
|
||||
scheme = "https"
|
||||
}
|
||||
}
|
||||
decl.BasePath = fmt.Sprintf("%s://%s", scheme, host)
|
||||
}
|
||||
resp.WriteAsJson(decl)
|
||||
}
|
||||
|
||||
func (sws SwaggerService) produceAllDeclarations() map[string]ApiDeclaration {
|
||||
decls := map[string]ApiDeclaration{}
|
||||
sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
|
||||
decls[k] = v
|
||||
})
|
||||
return decls
|
||||
}
|
||||
|
||||
func (sws SwaggerService) produceDeclarations(route string) (*ApiDeclaration, bool) {
|
||||
decl, ok := sws.apiDeclarationMap.At(route)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
decl.BasePath = sws.config.WebServicesUrl
|
||||
return &decl, true
|
||||
}
|
||||
|
||||
// composeDeclaration uses all routes and parameters to create a ApiDeclaration
|
||||
func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix string) ApiDeclaration {
|
||||
decl := ApiDeclaration{
|
||||
SwaggerVersion: swaggerVersion,
|
||||
BasePath: sws.config.WebServicesUrl,
|
||||
ResourcePath: pathPrefix,
|
||||
Models: ModelList{},
|
||||
ApiVersion: ws.Version()}
|
||||
|
||||
// collect any path parameters
|
||||
rootParams := []Parameter{}
|
||||
for _, param := range ws.PathParameters() {
|
||||
rootParams = append(rootParams, asSwaggerParameter(param.Data()))
|
||||
}
|
||||
// aggregate by path
|
||||
pathToRoutes := newOrderedRouteMap()
|
||||
for _, other := range ws.Routes() {
|
||||
if strings.HasPrefix(other.Path, pathPrefix) {
|
||||
if len(pathPrefix) > 1 && len(other.Path) > len(pathPrefix) && other.Path[len(pathPrefix)] != '/' {
|
||||
continue
|
||||
}
|
||||
pathToRoutes.Add(other.Path, other)
|
||||
}
|
||||
}
|
||||
pathToRoutes.Do(func(path string, routes []restful.Route) {
|
||||
api := Api{Path: strings.TrimSuffix(withoutWildcard(path), "/"), Description: ws.Documentation()}
|
||||
voidString := "void"
|
||||
for _, route := range routes {
|
||||
operation := Operation{
|
||||
Method: route.Method,
|
||||
Summary: route.Doc,
|
||||
Notes: route.Notes,
|
||||
// Type gets overwritten if there is a write sample
|
||||
DataTypeFields: DataTypeFields{Type: &voidString},
|
||||
Parameters: []Parameter{},
|
||||
Nickname: route.Operation,
|
||||
ResponseMessages: composeResponseMessages(route, &decl, &sws.config)}
|
||||
|
||||
operation.Consumes = route.Consumes
|
||||
operation.Produces = route.Produces
|
||||
|
||||
// share root params if any
|
||||
for _, swparam := range rootParams {
|
||||
operation.Parameters = append(operation.Parameters, swparam)
|
||||
}
|
||||
// route specific params
|
||||
for _, param := range route.ParameterDocs {
|
||||
operation.Parameters = append(operation.Parameters, asSwaggerParameter(param.Data()))
|
||||
}
|
||||
|
||||
sws.addModelsFromRouteTo(&operation, route, &decl)
|
||||
api.Operations = append(api.Operations, operation)
|
||||
}
|
||||
decl.Apis = append(decl.Apis, api)
|
||||
})
|
||||
return decl
|
||||
}
|
||||
|
||||
func withoutWildcard(path string) string {
|
||||
if strings.HasSuffix(path, ":*}") {
|
||||
return path[0:len(path)-3] + "}"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// composeResponseMessages takes the ResponseErrors (if any) and creates ResponseMessages from them.
|
||||
func composeResponseMessages(route restful.Route, decl *ApiDeclaration, config *Config) (messages []ResponseMessage) {
|
||||
if route.ResponseErrors == nil {
|
||||
return messages
|
||||
}
|
||||
// sort by code
|
||||
codes := sort.IntSlice{}
|
||||
for code := range route.ResponseErrors {
|
||||
codes = append(codes, code)
|
||||
}
|
||||
codes.Sort()
|
||||
for _, code := range codes {
|
||||
each := route.ResponseErrors[code]
|
||||
message := ResponseMessage{
|
||||
Code: code,
|
||||
Message: each.Message,
|
||||
}
|
||||
if each.Model != nil {
|
||||
st := reflect.TypeOf(each.Model)
|
||||
isCollection, st := detectCollectionType(st)
|
||||
// collection cannot be in responsemodel
|
||||
if !isCollection {
|
||||
modelName := modelBuilder{}.keyFrom(st)
|
||||
modelBuilder{Models: &decl.Models, Config: config}.addModel(st, "")
|
||||
message.ResponseModel = modelName
|
||||
}
|
||||
}
|
||||
messages = append(messages, message)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// addModelsFromRoute takes any read or write sample from the Route and creates a Swagger model from it.
|
||||
func (sws SwaggerService) addModelsFromRouteTo(operation *Operation, route restful.Route, decl *ApiDeclaration) {
|
||||
if route.ReadSample != nil {
|
||||
sws.addModelFromSampleTo(operation, false, route.ReadSample, &decl.Models)
|
||||
}
|
||||
if route.WriteSample != nil {
|
||||
sws.addModelFromSampleTo(operation, true, route.WriteSample, &decl.Models)
|
||||
}
|
||||
}
|
||||
|
||||
func detectCollectionType(st reflect.Type) (bool, reflect.Type) {
|
||||
isCollection := false
|
||||
if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
|
||||
st = st.Elem()
|
||||
isCollection = true
|
||||
} else {
|
||||
if st.Kind() == reflect.Ptr {
|
||||
if st.Elem().Kind() == reflect.Slice || st.Elem().Kind() == reflect.Array {
|
||||
st = st.Elem().Elem()
|
||||
isCollection = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return isCollection, st
|
||||
}
|
||||
|
||||
// addModelFromSample creates and adds (or overwrites) a Model from a sample resource
|
||||
func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse bool, sample interface{}, models *ModelList) {
|
||||
mb := modelBuilder{Models: models, Config: &sws.config}
|
||||
if isResponse {
|
||||
sampleType, items := asDataType(sample, &sws.config)
|
||||
operation.Type = sampleType
|
||||
operation.Items = items
|
||||
}
|
||||
mb.addModelFrom(sample)
|
||||
}
|
||||
|
||||
func asSwaggerParameter(param restful.ParameterData) Parameter {
|
||||
return Parameter{
|
||||
DataTypeFields: DataTypeFields{
|
||||
Type: ¶m.DataType,
|
||||
Format: asFormat(param.DataType, param.DataFormat),
|
||||
DefaultValue: Special(param.DefaultValue),
|
||||
},
|
||||
Name: param.Name,
|
||||
Description: param.Description,
|
||||
ParamType: asParamType(param.Kind),
|
||||
|
||||
Required: param.Required}
|
||||
}
|
||||
|
||||
// Between 1..7 path parameters is supported
|
||||
func composeRootPath(req *restful.Request) string {
|
||||
path := "/" + req.PathParameter("a")
|
||||
b := req.PathParameter("b")
|
||||
if b == "" {
|
||||
return path
|
||||
}
|
||||
path = path + "/" + b
|
||||
c := req.PathParameter("c")
|
||||
if c == "" {
|
||||
return path
|
||||
}
|
||||
path = path + "/" + c
|
||||
d := req.PathParameter("d")
|
||||
if d == "" {
|
||||
return path
|
||||
}
|
||||
path = path + "/" + d
|
||||
e := req.PathParameter("e")
|
||||
if e == "" {
|
||||
return path
|
||||
}
|
||||
path = path + "/" + e
|
||||
f := req.PathParameter("f")
|
||||
if f == "" {
|
||||
return path
|
||||
}
|
||||
path = path + "/" + f
|
||||
g := req.PathParameter("g")
|
||||
if g == "" {
|
||||
return path
|
||||
}
|
||||
return path + "/" + g
|
||||
}
|
||||
|
||||
func asFormat(dataType string, dataFormat string) string {
|
||||
if dataFormat != "" {
|
||||
return dataFormat
|
||||
}
|
||||
return "" // TODO
|
||||
}
|
||||
|
||||
func asParamType(kind int) string {
|
||||
switch {
|
||||
case kind == restful.PathParameterKind:
|
||||
return "path"
|
||||
case kind == restful.QueryParameterKind:
|
||||
return "query"
|
||||
case kind == restful.BodyParameterKind:
|
||||
return "body"
|
||||
case kind == restful.HeaderParameterKind:
|
||||
return "header"
|
||||
case kind == restful.FormParameterKind:
|
||||
return "form"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func asDataType(any interface{}, config *Config) (*string, *Item) {
|
||||
// If it's not a collection, return the suggested model name
|
||||
st := reflect.TypeOf(any)
|
||||
isCollection, st := detectCollectionType(st)
|
||||
modelName := modelBuilder{}.keyFrom(st)
|
||||
// if it's not a collection we are done
|
||||
if !isCollection {
|
||||
return &modelName, nil
|
||||
}
|
||||
|
||||
// XXX: This is not very elegant
|
||||
// We create an Item object referring to the given model
|
||||
models := ModelList{}
|
||||
mb := modelBuilder{Models: &models, Config: config}
|
||||
mb.addModelFrom(any)
|
||||
|
||||
elemTypeName := mb.getElementTypeName(modelName, "", st)
|
||||
item := new(Item)
|
||||
if mb.isPrimitiveType(elemTypeName) {
|
||||
mapped := mb.jsonSchemaType(elemTypeName)
|
||||
item.Type = &mapped
|
||||
} else {
|
||||
item.Ref = &elemTypeName
|
||||
}
|
||||
tmp := "array"
|
||||
return &tmp, item
|
||||
}
|
||||
5
vendor/github.com/emicklei/go-restful-swagger12/test_package/struct.go
generated
vendored
5
vendor/github.com/emicklei/go-restful-swagger12/test_package/struct.go
generated
vendored
@@ -1,5 +0,0 @@
|
||||
package test_package
|
||||
|
||||
type TestStruct struct {
|
||||
TestField string
|
||||
}
|
||||
86
vendor/github.com/emicklei/go-restful-swagger12/utils_test.go
generated
vendored
86
vendor/github.com/emicklei/go-restful-swagger12/utils_test.go
generated
vendored
@@ -1,86 +0,0 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testJsonFromStructWithConfig(t *testing.T, sample interface{}, expectedJson string, config *Config) bool {
|
||||
m := modelsFromStructWithConfig(sample, config)
|
||||
data, _ := json.MarshalIndent(m, " ", " ")
|
||||
return compareJson(t, string(data), expectedJson)
|
||||
}
|
||||
|
||||
func modelsFromStructWithConfig(sample interface{}, config *Config) *ModelList {
|
||||
models := new(ModelList)
|
||||
builder := modelBuilder{Models: models, Config: config}
|
||||
builder.addModelFrom(sample)
|
||||
return models
|
||||
}
|
||||
|
||||
func testJsonFromStruct(t *testing.T, sample interface{}, expectedJson string) bool {
|
||||
return testJsonFromStructWithConfig(t, sample, expectedJson, &Config{})
|
||||
}
|
||||
|
||||
func modelsFromStruct(sample interface{}) *ModelList {
|
||||
return modelsFromStructWithConfig(sample, &Config{})
|
||||
}
|
||||
|
||||
func compareJson(t *testing.T, actualJsonAsString string, expectedJsonAsString string) bool {
|
||||
success := false
|
||||
var actualMap map[string]interface{}
|
||||
json.Unmarshal([]byte(actualJsonAsString), &actualMap)
|
||||
var expectedMap map[string]interface{}
|
||||
err := json.Unmarshal([]byte(expectedJsonAsString), &expectedMap)
|
||||
if err != nil {
|
||||
var actualArray []interface{}
|
||||
json.Unmarshal([]byte(actualJsonAsString), &actualArray)
|
||||
var expectedArray []interface{}
|
||||
err := json.Unmarshal([]byte(expectedJsonAsString), &expectedArray)
|
||||
success = reflect.DeepEqual(actualArray, expectedArray)
|
||||
if err != nil {
|
||||
t.Fatalf("Unparsable expected JSON: %s, actual: %v, expected: %v", err, actualJsonAsString, expectedJsonAsString)
|
||||
}
|
||||
} else {
|
||||
success = reflect.DeepEqual(actualMap, expectedMap)
|
||||
}
|
||||
if !success {
|
||||
t.Log("---- expected -----")
|
||||
t.Log(withLineNumbers(expectedJsonAsString))
|
||||
t.Log("---- actual -----")
|
||||
t.Log(withLineNumbers(actualJsonAsString))
|
||||
t.Log("---- raw -----")
|
||||
t.Log(actualJsonAsString)
|
||||
t.Error("there are differences")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func indexOfNonMatchingLine(actual, expected string) int {
|
||||
a := strings.Split(actual, "\n")
|
||||
e := strings.Split(expected, "\n")
|
||||
size := len(a)
|
||||
if len(e) < len(a) {
|
||||
size = len(e)
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
if a[i] != e[i] {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func withLineNumbers(content string) string {
|
||||
var buffer bytes.Buffer
|
||||
lines := strings.Split(content, "\n")
|
||||
for i, each := range lines {
|
||||
buffer.WriteString(fmt.Sprintf("%d:%s\n", i, each))
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.x
|
||||
- 1.5
|
||||
- tip
|
||||
|
||||
27
vendor/github.com/pmezard/go-difflib/LICENSE
generated
vendored
Normal file
27
vendor/github.com/pmezard/go-difflib/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
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.
|
||||
50
vendor/github.com/pmezard/go-difflib/README.md
generated
vendored
Normal file
50
vendor/github.com/pmezard/go-difflib/README.md
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
go-difflib
|
||||
==========
|
||||
|
||||
[](https://travis-ci.org/pmezard/go-difflib)
|
||||
[](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
|
||||
```
|
||||
|
||||
772
vendor/github.com/pmezard/go-difflib/difflib/difflib.go
generated
vendored
Normal file
772
vendor/github.com/pmezard/go-difflib/difflib/difflib.go
generated
vendored
Normal file
@@ -0,0 +1,772 @@
|
||||
// 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()
|
||||
wf := func(format string, args ...interface{}) error {
|
||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
||||
return err
|
||||
}
|
||||
ws := func(s string) error {
|
||||
_, err := buf.WriteString(s)
|
||||
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
|
||||
}
|
||||
if diff.FromFile != "" || diff.ToFile != "" {
|
||||
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = wf("+++ %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 := wf("@@ -%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 := ws(" " + line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if c.Tag == 'r' || c.Tag == 'd' {
|
||||
for _, line := range diff.A[i1:i2] {
|
||||
if err := ws("-" + line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.Tag == 'r' || c.Tag == 'i' {
|
||||
for _, line := range diff.B[j1:j2] {
|
||||
if err := ws("+" + 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
|
||||
wf := func(format string, args ...interface{}) {
|
||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
||||
if diffErr == nil && err != nil {
|
||||
diffErr = err
|
||||
}
|
||||
}
|
||||
ws := func(s string) {
|
||||
_, err := buf.WriteString(s)
|
||||
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
|
||||
}
|
||||
if diff.FromFile != "" || diff.ToFile != "" {
|
||||
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
||||
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
|
||||
}
|
||||
}
|
||||
|
||||
first, last := g[0], g[len(g)-1]
|
||||
ws("***************" + diff.Eol)
|
||||
|
||||
range1 := formatRangeContext(first.I1, last.I2)
|
||||
wf("*** %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] {
|
||||
ws(prefix[cc.Tag] + line)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
range2 := formatRangeContext(first.J1, last.J2)
|
||||
wf("--- %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] {
|
||||
ws(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
|
||||
}
|
||||
426
vendor/github.com/pmezard/go-difflib/difflib/difflib_test.go
generated
vendored
Normal file
426
vendor/github.com/pmezard/go-difflib/difflib/difflib_test.go
generated
vendored
Normal file
@@ -0,0 +1,426 @@
|
||||
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 ExampleGetUnifiedDiffCode() {
|
||||
a := `one
|
||||
two
|
||||
three
|
||||
four
|
||||
fmt.Printf("%s,%T",a,b)`
|
||||
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.Println(strings.Replace(result, "\t", " ", -1))
|
||||
// Output:
|
||||
// --- Original 2005-01-26 23:30:50
|
||||
// +++ Current 2010-04-02 10:20:52
|
||||
// @@ -1,5 +1,4 @@
|
||||
// +zero
|
||||
// one
|
||||
// -two
|
||||
// three
|
||||
// four
|
||||
// -fmt.Printf("%s,%T",a,b)
|
||||
}
|
||||
|
||||
func ExampleGetContextDiffCode() {
|
||||
a := `one
|
||||
two
|
||||
three
|
||||
four
|
||||
fmt.Printf("%s,%T",a,b)`
|
||||
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.Print(strings.Replace(result, "\t", " ", -1))
|
||||
// Output:
|
||||
// *** Original
|
||||
// --- Current
|
||||
// ***************
|
||||
// *** 1,5 ****
|
||||
// one
|
||||
// ! two
|
||||
// ! three
|
||||
// four
|
||||
// - fmt.Printf("%s,%T",a,b)
|
||||
// --- 1,4 ----
|
||||
// + zero
|
||||
// one
|
||||
// ! tree
|
||||
// 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 TestOmitFilenames(t *testing.T) {
|
||||
diff := UnifiedDiff{
|
||||
A: SplitLines("o\nn\ne\n"),
|
||||
B: SplitLines("t\nw\no\n"),
|
||||
Eol: "\n",
|
||||
}
|
||||
ud, err := GetUnifiedDiffString(diff)
|
||||
assertEqual(t, err, nil)
|
||||
assertEqual(t, SplitLines(ud), []string{
|
||||
"@@ -0,0 +1,2 @@\n",
|
||||
"+t\n",
|
||||
"+w\n",
|
||||
"@@ -2,2 +3,0 @@\n",
|
||||
"-n\n",
|
||||
"-e\n",
|
||||
"\n",
|
||||
})
|
||||
|
||||
cd, err := GetContextDiffString(ContextDiff(diff))
|
||||
assertEqual(t, err, nil)
|
||||
assertEqual(t, SplitLines(cd), []string{
|
||||
"***************\n",
|
||||
"*** 0 ****\n",
|
||||
"--- 1,2 ----\n",
|
||||
"+ t\n",
|
||||
"+ w\n",
|
||||
"***************\n",
|
||||
"*** 2,3 ****\n",
|
||||
"- n\n",
|
||||
"- e\n",
|
||||
"--- 3 ----\n",
|
||||
"\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)
|
||||
}
|
||||
24
vendor/github.com/stretchr/testify/.gitignore
generated
vendored
Normal file
24
vendor/github.com/stretchr/testify/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# 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
|
||||
7
vendor/github.com/stretchr/testify/.travis.gofmt.sh
generated
vendored
Executable file
7
vendor/github.com/stretchr/testify/.travis.gofmt.sh
generated
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -n "$(gofmt -l .)" ]; then
|
||||
echo "Go code is not formatted:"
|
||||
gofmt -d .
|
||||
exit 1
|
||||
fi
|
||||
13
vendor/github.com/stretchr/testify/.travis.gogenerate.sh
generated
vendored
Executable file
13
vendor/github.com/stretchr/testify/.travis.gogenerate.sh
generated
vendored
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ "$TRAVIS_GO_VERSION" =~ ^1\.[45](\..*)?$ ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
go get github.com/ernesto-jimenez/gogen/imports
|
||||
go generate ./...
|
||||
if [ -n "$(git diff)" ]; then
|
||||
echo "Go generate had not been run"
|
||||
git diff
|
||||
exit 1
|
||||
fi
|
||||
10
vendor/github.com/stretchr/testify/.travis.govet.sh
generated
vendored
Executable file
10
vendor/github.com/stretchr/testify/.travis.govet.sh
generated
vendored
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname $0)"
|
||||
DIRS=". assert require mock _codegen"
|
||||
set -e
|
||||
for subdir in $DIRS; do
|
||||
pushd $subdir
|
||||
go vet
|
||||
popd
|
||||
done
|
||||
15
vendor/github.com/stretchr/testify/.travis.yml
generated
vendored
Normal file
15
vendor/github.com/stretchr/testify/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
language: go
|
||||
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
- tip
|
||||
|
||||
script:
|
||||
- ./.travis.gogenerate.sh
|
||||
- ./.travis.gofmt.sh
|
||||
- ./.travis.govet.sh
|
||||
- go test -v -race $(go list ./... | grep -v vendor)
|
||||
27
vendor/github.com/stretchr/testify/Gopkg.lock
generated
vendored
Normal file
27
vendor/github.com/stretchr/testify/Gopkg.lock
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/objx"
|
||||
packages = ["."]
|
||||
revision = "facf9a85c22f48d2f52f2380e4efce1768749a89"
|
||||
version = "v0.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "448ddae4702c6aded2555faafd390c537789bb1c483f70b0431e6634f73f2090"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
16
vendor/github.com/stretchr/testify/Gopkg.toml
generated
vendored
Normal file
16
vendor/github.com/stretchr/testify/Gopkg.toml
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
[prune]
|
||||
unused-packages = true
|
||||
non-go = true
|
||||
go-tests = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
version = "~1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
version = "~1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/objx"
|
||||
version = "~0.1.0"
|
||||
22
vendor/github.com/stretchr/testify/LICENSE
generated
vendored
Normal file
22
vendor/github.com/stretchr/testify/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
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.
|
||||
301
vendor/github.com/stretchr/testify/README.md
generated
vendored
Normal file
301
vendor/github.com/stretchr/testify/README.md
generated
vendored
Normal file
@@ -0,0 +1,301 @@
|
||||
Testify - Thou Shalt Write Tests
|
||||
================================
|
||||
|
||||
[](https://travis-ci.org/stretchr/testify) [](https://goreportcard.com/report/github.com/stretchr/testify) [](https://godoc.org/github.com/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)
|
||||
* [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.
|
||||
|
||||
[`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`:
|
||||
|
||||
go get github.com/stretchr/testify
|
||||
|
||||
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`.
|
||||
|
||||
------
|
||||
|
||||
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.
|
||||
316
vendor/github.com/stretchr/testify/_codegen/main.go
generated
vendored
Normal file
316
vendor/github.com/stretchr/testify/_codegen/main.go
generated
vendored
Normal file
@@ -0,0 +1,316 @@
|
||||
// This program reads all assertion functions from the assert package and
|
||||
// automatically generates the corresponding requires and forwarded assertions
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/doc"
|
||||
"go/format"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"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")
|
||||
includeF = flag.Bool("include-format-funcs", false, "include format functions such as Errorf and Equalf")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
code, err := format.Source(buff.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write file
|
||||
output, err := outputFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer output.Close()
|
||||
_, err = io.Copy(output, bytes.NewReader(code))
|
||||
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 signature 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
|
||||
}
|
||||
|
||||
// Skip functions ending with f
|
||||
if strings.HasSuffix(fdocs.Name, "f") && !*includeF {
|
||||
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 package
|
||||
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) ParamsFormat() string {
|
||||
return strings.Replace(f.Params(), "msgAndArgs", "msg string, args", 1)
|
||||
}
|
||||
|
||||
func (f *testFunc) ForwardedParamsFormat() string {
|
||||
return strings.Replace(f.ForwardedParams(), "msgAndArgs", "append([]interface{}{msg}, args...)", 1)
|
||||
}
|
||||
|
||||
func (f *testFunc) Comment() string {
|
||||
return "// " + strings.Replace(strings.TrimSpace(f.DocInfo.Doc), "\n", "\n// ", -1)
|
||||
}
|
||||
|
||||
func (f *testFunc) CommentFormat() string {
|
||||
search := fmt.Sprintf("%s", f.DocInfo.Name)
|
||||
replace := fmt.Sprintf("%sf", f.DocInfo.Name)
|
||||
comment := strings.Replace(f.Comment(), search, replace, -1)
|
||||
exp := regexp.MustCompile(replace + `\(((\(\)|[^)])+)\)`)
|
||||
return exp.ReplaceAllString(comment, replace+`($1, "error message %s", "formatted")`)
|
||||
}
|
||||
|
||||
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}})
|
||||
}`
|
||||
349
vendor/github.com/stretchr/testify/assert/assertion_format.go
generated
vendored
Normal file
349
vendor/github.com/stretchr/testify/assert/assertion_format.go
generated
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
* 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"
|
||||
)
|
||||
|
||||
// Conditionf uses a Comparison to assert a complex condition.
|
||||
func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool {
|
||||
return Condition(t, comp, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Containsf asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
|
||||
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
|
||||
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
|
||||
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
|
||||
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||
return DirExists(t, path, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
|
||||
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||
// the number of appearances of each of them in both lists should match.
|
||||
//
|
||||
// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
|
||||
func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
|
||||
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// assert.Emptyf(t, obj, "error message %s", "formatted")
|
||||
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
return Empty(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Equalf asserts that two objects are equal.
|
||||
//
|
||||
// assert.Equalf(t, 123, 123, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
// cannot be determined and will always fail.
|
||||
func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
|
||||
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
|
||||
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123))
|
||||
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.Errorf(t, err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedErrorf, err)
|
||||
// }
|
||||
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||
return Error(t, err, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
|
||||
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Failf reports a failure through
|
||||
func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
||||
return Fail(t, failureMessage, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// FailNowf fails test
|
||||
func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
||||
return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Falsef asserts that the specified value is false.
|
||||
//
|
||||
// assert.Falsef(t, myBool, "error message %s", "formatted")
|
||||
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||
return False(t, value, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
|
||||
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||
return FileExists(t, path, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPBodyContainsf asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// assert.HTTPBodyContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPBodyNotContainsf asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// assert.HTTPBodyNotContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPErrorf asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
|
||||
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InDeltaf asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
|
||||
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||
func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InDeltaSlicef is the same as InDelta, except it compares two slices.
|
||||
func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
|
||||
func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||
return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
|
||||
func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// IsTypef asserts that the specified objects are of the same type.
|
||||
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// JSONEqf asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
||||
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Lenf asserts that the specified object has specific length.
|
||||
// Lenf also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
|
||||
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
|
||||
return Len(t, object, length, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Nilf asserts that the specified object is nil.
|
||||
//
|
||||
// assert.Nilf(t, err, "error message %s", "formatted")
|
||||
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
return Nil(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NoErrorf asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.NoErrorf(t, err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||
return NoError(t, err, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
|
||||
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotEmptyf 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 assert.NotEmptyf(t, obj, "error message %s", "formatted") {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
return NotEmpty(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotEqualf asserts that the specified values are NOT equal.
|
||||
//
|
||||
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotNilf asserts that the specified object is not nil.
|
||||
//
|
||||
// assert.NotNilf(t, err, "error message %s", "formatted")
|
||||
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
return NotNil(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
|
||||
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
return NotPanics(t, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
|
||||
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
|
||||
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotSubsetf asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
|
||||
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotZerof asserts that i is not the zero value for its type.
|
||||
func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||
return NotZero(t, i, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
return Panics(t, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
|
||||
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
|
||||
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Subsetf asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
|
||||
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
return Subset(t, list, subset, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Truef asserts that the specified value is true.
|
||||
//
|
||||
// assert.Truef(t, myBool, "error message %s", "formatted")
|
||||
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||
return True(t, value, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// WithinDurationf asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
||||
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
|
||||
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Zerof asserts that i is the zero value for its type.
|
||||
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||
return Zero(t, i, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
4
vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl
generated
vendored
Normal file
4
vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{{.CommentFormat}}
|
||||
func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool {
|
||||
return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}})
|
||||
}
|
||||
686
vendor/github.com/stretchr/testify/assert/assertion_forward.go
generated
vendored
Normal file
686
vendor/github.com/stretchr/testify/assert/assertion_forward.go
generated
vendored
Normal file
@@ -0,0 +1,686 @@
|
||||
/*
|
||||
* 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...)
|
||||
}
|
||||
|
||||
// Conditionf uses a Comparison to assert a complex condition.
|
||||
func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{}) bool {
|
||||
return Conditionf(a.t, comp, msg, args...)
|
||||
}
|
||||
|
||||
// Contains asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.Contains("Hello World", "World")
|
||||
// a.Contains(["Hello", "World"], "World")
|
||||
// a.Contains({"Hello": "World"}, "Hello")
|
||||
func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
|
||||
return Contains(a.t, s, contains, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Containsf asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.Containsf("Hello World", "World", "error message %s", "formatted")
|
||||
// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted")
|
||||
// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted")
|
||||
func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
return Containsf(a.t, s, contains, msg, args...)
|
||||
}
|
||||
|
||||
// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
|
||||
func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) bool {
|
||||
return DirExists(a.t, path, msgAndArgs...)
|
||||
}
|
||||
|
||||
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
|
||||
func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) bool {
|
||||
return DirExistsf(a.t, path, msg, args...)
|
||||
}
|
||||
|
||||
// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified
|
||||
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||
// the number of appearances of each of them in both lists should match.
|
||||
//
|
||||
// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2])
|
||||
func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) bool {
|
||||
return ElementsMatch(a.t, listA, listB, msgAndArgs...)
|
||||
}
|
||||
|
||||
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
|
||||
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||
// the number of appearances of each of them in both lists should match.
|
||||
//
|
||||
// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
|
||||
func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
|
||||
return ElementsMatchf(a.t, listA, listB, msg, args...)
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return Empty(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// a.Emptyf(obj, "error message %s", "formatted")
|
||||
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool {
|
||||
return Emptyf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// Equal asserts that two objects are equal.
|
||||
//
|
||||
// a.Equal(123, 123)
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
// cannot be determined and will always fail.
|
||||
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()
|
||||
// a.EqualError(err, expectedErrorString)
|
||||
func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool {
|
||||
return EqualError(a.t, theError, errString, msgAndArgs...)
|
||||
}
|
||||
|
||||
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted")
|
||||
func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) bool {
|
||||
return EqualErrorf(a.t, theError, errString, msg, args...)
|
||||
}
|
||||
|
||||
// EqualValues asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// a.EqualValues(uint32(123), int32(123))
|
||||
func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
return EqualValues(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123))
|
||||
func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
return EqualValuesf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Equalf asserts that two objects are equal.
|
||||
//
|
||||
// a.Equalf(123, 123, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
// cannot be determined and will always fail.
|
||||
func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
return Equalf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Error asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.Error(err) {
|
||||
// assert.Equal(t, expectedError, err)
|
||||
// }
|
||||
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
|
||||
return Error(a.t, err, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.Errorf(err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedErrorf, err)
|
||||
// }
|
||||
func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool {
|
||||
return Errorf(a.t, err, msg, args...)
|
||||
}
|
||||
|
||||
// Exactly asserts that two objects are equal in value and type.
|
||||
//
|
||||
// a.Exactly(int32(123), int64(123))
|
||||
func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
return Exactly(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123))
|
||||
func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
return Exactlyf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// FailNowf fails test
|
||||
func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) bool {
|
||||
return FailNowf(a.t, failureMessage, msg, args...)
|
||||
}
|
||||
|
||||
// Failf reports a failure through
|
||||
func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) bool {
|
||||
return Failf(a.t, failureMessage, msg, args...)
|
||||
}
|
||||
|
||||
// False asserts that the specified value is false.
|
||||
//
|
||||
// a.False(myBool)
|
||||
func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool {
|
||||
return False(a.t, value, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Falsef asserts that the specified value is false.
|
||||
//
|
||||
// a.Falsef(myBool, "error message %s", "formatted")
|
||||
func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) bool {
|
||||
return Falsef(a.t, value, msg, args...)
|
||||
}
|
||||
|
||||
// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
|
||||
func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) bool {
|
||||
return FileExists(a.t, path, msgAndArgs...)
|
||||
}
|
||||
|
||||
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
|
||||
func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) bool {
|
||||
return FileExistsf(a.t, path, msg, args...)
|
||||
}
|
||||
|
||||
// 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{}, msgAndArgs ...interface{}) bool {
|
||||
return HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPBodyContainsf asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// a.HTTPBodyContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
return HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...)
|
||||
}
|
||||
|
||||
// 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{}, msgAndArgs ...interface{}) bool {
|
||||
return HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPBodyNotContainsf asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// a.HTTPBodyNotContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
return HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...)
|
||||
}
|
||||
|
||||
// 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, msgAndArgs ...interface{}) bool {
|
||||
return HTTPError(a.t, handler, method, url, values, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPErrorf asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
return HTTPErrorf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// 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, msgAndArgs ...interface{}) bool {
|
||||
return HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
return HTTPRedirectf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// 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, msgAndArgs ...interface{}) bool {
|
||||
return HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
return HTTPSuccessf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// Implements asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// a.Implements((*MyInterface)(nil), new(MyObject))
|
||||
func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return Implements(a.t, interfaceObject, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
|
||||
func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
return Implementsf(a.t, interfaceObject, object, msg, args...)
|
||||
}
|
||||
|
||||
// InDelta asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// a.InDelta(math.Pi, (22 / 7.0), 0.01)
|
||||
func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
|
||||
return InDelta(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||
func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
|
||||
return InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||
func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
return InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// InDeltaSlicef is the same as InDelta, except it compares two slices.
|
||||
func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
return InDeltaSlicef(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// InDeltaf asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
|
||||
func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
return InDeltaf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// InEpsilon asserts that expected and actual have a relative error less than epsilon
|
||||
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 each value from two slices.
|
||||
func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
|
||||
return InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
|
||||
func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||
return InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...)
|
||||
}
|
||||
|
||||
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
|
||||
func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||
return InEpsilonf(a.t, expected, actual, epsilon, msg, args...)
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// IsTypef asserts that the specified objects are of the same type.
|
||||
func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
return IsTypef(a.t, expectedType, object, msg, args...)
|
||||
}
|
||||
|
||||
// JSONEq asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
||||
func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool {
|
||||
return JSONEq(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// JSONEqf asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
||||
func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool {
|
||||
return JSONEqf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool {
|
||||
return Len(a.t, object, length, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Lenf asserts that the specified object has specific length.
|
||||
// Lenf also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// a.Lenf(mySlice, 3, "error message %s", "formatted")
|
||||
func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) bool {
|
||||
return Lenf(a.t, object, length, msg, args...)
|
||||
}
|
||||
|
||||
// Nil asserts that the specified object is nil.
|
||||
//
|
||||
// a.Nil(err)
|
||||
func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return Nil(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Nilf asserts that the specified object is nil.
|
||||
//
|
||||
// a.Nilf(err, "error message %s", "formatted")
|
||||
func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) bool {
|
||||
return Nilf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// NoError asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.NoError(err) {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool {
|
||||
return NoError(a.t, err, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NoErrorf asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.NoErrorf(err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool {
|
||||
return NoErrorf(a.t, err, msg, args...)
|
||||
}
|
||||
|
||||
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.NotContains("Hello World", "Earth")
|
||||
// a.NotContains(["Hello", "World"], "Earth")
|
||||
// a.NotContains({"Hello": "World"}, "Earth")
|
||||
func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
|
||||
return NotContains(a.t, s, contains, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted")
|
||||
// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted")
|
||||
// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted")
|
||||
func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
return NotContainsf(a.t, s, contains, msg, args...)
|
||||
}
|
||||
|
||||
// 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])
|
||||
// }
|
||||
func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return NotEmpty(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotEmptyf 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.NotEmptyf(obj, "error message %s", "formatted") {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) bool {
|
||||
return NotEmptyf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// NotEqual asserts that the specified values are NOT equal.
|
||||
//
|
||||
// a.NotEqual(obj1, obj2)
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
return NotEqual(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotEqualf asserts that the specified values are NOT equal.
|
||||
//
|
||||
// a.NotEqualf(obj1, obj2, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
return NotEqualf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// NotNil asserts that the specified object is not nil.
|
||||
//
|
||||
// a.NotNil(err)
|
||||
func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return NotNil(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotNilf asserts that the specified object is not nil.
|
||||
//
|
||||
// a.NotNilf(err, "error message %s", "formatted")
|
||||
func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) bool {
|
||||
return NotNilf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// a.NotPanics(func(){ RemainCalm() })
|
||||
func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
||||
return NotPanics(a.t, f, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted")
|
||||
func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
return NotPanicsf(a.t, f, msg, args...)
|
||||
}
|
||||
|
||||
// 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")
|
||||
func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
return NotRegexp(a.t, rx, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
|
||||
// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted")
|
||||
func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
return NotRegexpf(a.t, rx, str, msg, args...)
|
||||
}
|
||||
|
||||
// NotSubset asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
|
||||
func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
|
||||
return NotSubset(a.t, list, subset, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotSubsetf asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
|
||||
func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
return NotSubsetf(a.t, list, subset, msg, args...)
|
||||
}
|
||||
|
||||
// NotZero asserts that i is not the zero value for its type.
|
||||
func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool {
|
||||
return NotZero(a.t, i, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotZerof asserts that i is not the zero value for its type.
|
||||
func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bool {
|
||||
return NotZerof(a.t, i, msg, args...)
|
||||
}
|
||||
|
||||
// Panics asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// a.Panics(func(){ GoCrazy() })
|
||||
func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
||||
return Panics(a.t, f, msgAndArgs...)
|
||||
}
|
||||
|
||||
// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// a.PanicsWithValue("crazy error", func(){ GoCrazy() })
|
||||
func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
||||
return PanicsWithValue(a.t, expected, f, msgAndArgs...)
|
||||
}
|
||||
|
||||
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
return PanicsWithValuef(a.t, expected, f, msg, args...)
|
||||
}
|
||||
|
||||
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
return Panicsf(a.t, f, msg, args...)
|
||||
}
|
||||
|
||||
// Regexp asserts that a specified regexp matches a string.
|
||||
//
|
||||
// a.Regexp(regexp.MustCompile("start"), "it's starting")
|
||||
// a.Regexp("start...$", "it's not starting")
|
||||
func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
return Regexp(a.t, rx, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
|
||||
// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted")
|
||||
func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
return Regexpf(a.t, rx, str, msg, args...)
|
||||
}
|
||||
|
||||
// Subset asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
|
||||
func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
|
||||
return Subset(a.t, list, subset, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Subsetf asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
|
||||
func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
return Subsetf(a.t, list, subset, msg, args...)
|
||||
}
|
||||
|
||||
// True asserts that the specified value is true.
|
||||
//
|
||||
// a.True(myBool)
|
||||
func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool {
|
||||
return True(a.t, value, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Truef asserts that the specified value is true.
|
||||
//
|
||||
// a.Truef(myBool, "error message %s", "formatted")
|
||||
func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool {
|
||||
return Truef(a.t, value, msg, args...)
|
||||
}
|
||||
|
||||
// WithinDuration asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// a.WithinDuration(time.Now(), time.Now(), 10*time.Second)
|
||||
func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool {
|
||||
return WithinDuration(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// WithinDurationf asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
||||
func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
|
||||
return WithinDurationf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// Zero asserts that i is the zero value for its type.
|
||||
func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool {
|
||||
return Zero(a.t, i, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Zerof asserts that i is the zero value for its type.
|
||||
func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) bool {
|
||||
return Zerof(a.t, i, msg, args...)
|
||||
}
|
||||
4
vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl
generated
vendored
Normal file
4
vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{{.CommentWithoutT "a"}}
|
||||
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
|
||||
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
||||
}
|
||||
1256
vendor/github.com/stretchr/testify/assert/assertions.go
generated
vendored
Normal file
1256
vendor/github.com/stretchr/testify/assert/assertions.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1581
vendor/github.com/stretchr/testify/assert/assertions_test.go
generated
vendored
Normal file
1581
vendor/github.com/stretchr/testify/assert/assertions_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
45
vendor/github.com/stretchr/testify/assert/doc.go
generated
vendored
Normal file
45
vendor/github.com/stretchr/testify/assert/doc.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
|
||||
//
|
||||
// Example Usage
|
||||
//
|
||||
// The following is a complete example using assert in a standard test function:
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// assert.Equal(t, a, b, "The two words should be the same.")
|
||||
//
|
||||
// }
|
||||
//
|
||||
// if you assert many times, use the format below:
|
||||
//
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
// assert := assert.New(t)
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// assert.Equal(a, b, "The two words should be the same.")
|
||||
// }
|
||||
//
|
||||
// Assertions
|
||||
//
|
||||
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
|
||||
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
|
||||
// testing framework. This allows the assertion funcs to write the failings and other details to
|
||||
// the correct place.
|
||||
//
|
||||
// Every assertion function also takes an optional string message as the final argument,
|
||||
// allowing custom error messages to be appended to the message the assertion method outputs.
|
||||
package assert
|
||||
10
vendor/github.com/stretchr/testify/assert/errors.go
generated
vendored
Normal file
10
vendor/github.com/stretchr/testify/assert/errors.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
package assert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// AnError is an error instance useful for testing. If the code does not care
|
||||
// about error specifics, and only needs to return the error for example, this
|
||||
// error should be used to make the test code more readable.
|
||||
var AnError = errors.New("assert.AnError general error for testing")
|
||||
16
vendor/github.com/stretchr/testify/assert/forward_assertions.go
generated
vendored
Normal file
16
vendor/github.com/stretchr/testify/assert/forward_assertions.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package assert
|
||||
|
||||
// Assertions provides assertion methods around the
|
||||
// TestingT interface.
|
||||
type Assertions struct {
|
||||
t TestingT
|
||||
}
|
||||
|
||||
// New makes a new Assertions object for the specified TestingT.
|
||||
func New(t TestingT) *Assertions {
|
||||
return &Assertions{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs
|
||||
611
vendor/github.com/stretchr/testify/assert/forward_assertions_test.go
generated
vendored
Normal file
611
vendor/github.com/stretchr/testify/assert/forward_assertions_test.go
generated
vendored
Normal file
@@ -0,0 +1,611 @@
|
||||
package assert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestImplementsWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.Implements((*AssertionTesterInterface)(nil), new(AssertionTesterConformingObject)) {
|
||||
t.Error("Implements method should return true: AssertionTesterConformingObject implements AssertionTesterInterface")
|
||||
}
|
||||
if assert.Implements((*AssertionTesterInterface)(nil), new(AssertionTesterNonConformingObject)) {
|
||||
t.Error("Implements method should return false: AssertionTesterNonConformingObject does not implements AssertionTesterInterface")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsTypeWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.IsType(new(AssertionTesterConformingObject), new(AssertionTesterConformingObject)) {
|
||||
t.Error("IsType should return true: AssertionTesterConformingObject is the same type as AssertionTesterConformingObject")
|
||||
}
|
||||
if assert.IsType(new(AssertionTesterConformingObject), new(AssertionTesterNonConformingObject)) {
|
||||
t.Error("IsType should return false: AssertionTesterConformingObject is not the same type as AssertionTesterNonConformingObject")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEqualWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.Equal("Hello World", "Hello World") {
|
||||
t.Error("Equal should return true")
|
||||
}
|
||||
if !assert.Equal(123, 123) {
|
||||
t.Error("Equal should return true")
|
||||
}
|
||||
if !assert.Equal(123.5, 123.5) {
|
||||
t.Error("Equal should return true")
|
||||
}
|
||||
if !assert.Equal([]byte("Hello World"), []byte("Hello World")) {
|
||||
t.Error("Equal should return true")
|
||||
}
|
||||
if !assert.Equal(nil, nil) {
|
||||
t.Error("Equal should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqualValuesWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.EqualValues(uint32(10), int32(10)) {
|
||||
t.Error("EqualValues should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotNilWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.NotNil(new(AssertionTesterConformingObject)) {
|
||||
t.Error("NotNil should return true: object is not nil")
|
||||
}
|
||||
if assert.NotNil(nil) {
|
||||
t.Error("NotNil should return false: object is nil")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNilWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.Nil(nil) {
|
||||
t.Error("Nil should return true: object is nil")
|
||||
}
|
||||
if assert.Nil(new(AssertionTesterConformingObject)) {
|
||||
t.Error("Nil should return false: object is not nil")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTrueWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.True(true) {
|
||||
t.Error("True should return true")
|
||||
}
|
||||
if assert.True(false) {
|
||||
t.Error("True should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFalseWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.False(false) {
|
||||
t.Error("False should return true")
|
||||
}
|
||||
if assert.False(true) {
|
||||
t.Error("False should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestExactlyWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
a := float32(1)
|
||||
b := float64(1)
|
||||
c := float32(1)
|
||||
d := float32(2)
|
||||
|
||||
if assert.Exactly(a, b) {
|
||||
t.Error("Exactly should return false")
|
||||
}
|
||||
if assert.Exactly(a, d) {
|
||||
t.Error("Exactly should return false")
|
||||
}
|
||||
if !assert.Exactly(a, c) {
|
||||
t.Error("Exactly should return true")
|
||||
}
|
||||
|
||||
if assert.Exactly(nil, a) {
|
||||
t.Error("Exactly should return false")
|
||||
}
|
||||
if assert.Exactly(a, nil) {
|
||||
t.Error("Exactly should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNotEqualWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.NotEqual("Hello World", "Hello World!") {
|
||||
t.Error("NotEqual should return true")
|
||||
}
|
||||
if !assert.NotEqual(123, 1234) {
|
||||
t.Error("NotEqual should return true")
|
||||
}
|
||||
if !assert.NotEqual(123.5, 123.55) {
|
||||
t.Error("NotEqual should return true")
|
||||
}
|
||||
if !assert.NotEqual([]byte("Hello World"), []byte("Hello World!")) {
|
||||
t.Error("NotEqual should return true")
|
||||
}
|
||||
if !assert.NotEqual(nil, new(AssertionTesterConformingObject)) {
|
||||
t.Error("NotEqual should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
list := []string{"Foo", "Bar"}
|
||||
|
||||
if !assert.Contains("Hello World", "Hello") {
|
||||
t.Error("Contains should return true: \"Hello World\" contains \"Hello\"")
|
||||
}
|
||||
if assert.Contains("Hello World", "Salut") {
|
||||
t.Error("Contains should return false: \"Hello World\" does not contain \"Salut\"")
|
||||
}
|
||||
|
||||
if !assert.Contains(list, "Foo") {
|
||||
t.Error("Contains should return true: \"[\"Foo\", \"Bar\"]\" contains \"Foo\"")
|
||||
}
|
||||
if assert.Contains(list, "Salut") {
|
||||
t.Error("Contains should return false: \"[\"Foo\", \"Bar\"]\" does not contain \"Salut\"")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNotContainsWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
list := []string{"Foo", "Bar"}
|
||||
|
||||
if !assert.NotContains("Hello World", "Hello!") {
|
||||
t.Error("NotContains should return true: \"Hello World\" does not contain \"Hello!\"")
|
||||
}
|
||||
if assert.NotContains("Hello World", "Hello") {
|
||||
t.Error("NotContains should return false: \"Hello World\" contains \"Hello\"")
|
||||
}
|
||||
|
||||
if !assert.NotContains(list, "Foo!") {
|
||||
t.Error("NotContains should return true: \"[\"Foo\", \"Bar\"]\" does not contain \"Foo!\"")
|
||||
}
|
||||
if assert.NotContains(list, "Foo") {
|
||||
t.Error("NotContains should return false: \"[\"Foo\", \"Bar\"]\" contains \"Foo\"")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestConditionWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.Condition(func() bool { return true }, "Truth") {
|
||||
t.Error("Condition should return true")
|
||||
}
|
||||
|
||||
if assert.Condition(func() bool { return false }, "Lie") {
|
||||
t.Error("Condition should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDidPanicWrapper(t *testing.T) {
|
||||
|
||||
if funcDidPanic, _ := didPanic(func() {
|
||||
panic("Panic!")
|
||||
}); !funcDidPanic {
|
||||
t.Error("didPanic should return true")
|
||||
}
|
||||
|
||||
if funcDidPanic, _ := didPanic(func() {
|
||||
}); funcDidPanic {
|
||||
t.Error("didPanic should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPanicsWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.Panics(func() {
|
||||
panic("Panic!")
|
||||
}) {
|
||||
t.Error("Panics should return true")
|
||||
}
|
||||
|
||||
if assert.Panics(func() {
|
||||
}) {
|
||||
t.Error("Panics should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNotPanicsWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.NotPanics(func() {
|
||||
}) {
|
||||
t.Error("NotPanics should return true")
|
||||
}
|
||||
|
||||
if assert.NotPanics(func() {
|
||||
panic("Panic!")
|
||||
}) {
|
||||
t.Error("NotPanics should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNoErrorWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
// start with a nil error
|
||||
var err error
|
||||
|
||||
assert.True(mockAssert.NoError(err), "NoError should return True for nil arg")
|
||||
|
||||
// now set an error
|
||||
err = errors.New("Some error")
|
||||
|
||||
assert.False(mockAssert.NoError(err), "NoError with error should return False")
|
||||
|
||||
}
|
||||
|
||||
func TestErrorWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
// start with a nil error
|
||||
var err error
|
||||
|
||||
assert.False(mockAssert.Error(err), "Error should return False for nil arg")
|
||||
|
||||
// now set an error
|
||||
err = errors.New("Some error")
|
||||
|
||||
assert.True(mockAssert.Error(err), "Error with error should return True")
|
||||
|
||||
}
|
||||
|
||||
func TestEqualErrorWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
// start with a nil error
|
||||
var err error
|
||||
assert.False(mockAssert.EqualError(err, ""),
|
||||
"EqualError should return false for nil arg")
|
||||
|
||||
// now set an error
|
||||
err = errors.New("some error")
|
||||
assert.False(mockAssert.EqualError(err, "Not some error"),
|
||||
"EqualError should return false for different error string")
|
||||
assert.True(mockAssert.EqualError(err, "some error"),
|
||||
"EqualError should return true")
|
||||
}
|
||||
|
||||
func TestEmptyWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
assert.True(mockAssert.Empty(""), "Empty string is empty")
|
||||
assert.True(mockAssert.Empty(nil), "Nil is empty")
|
||||
assert.True(mockAssert.Empty([]string{}), "Empty string array is empty")
|
||||
assert.True(mockAssert.Empty(0), "Zero int value is empty")
|
||||
assert.True(mockAssert.Empty(false), "False value is empty")
|
||||
|
||||
assert.False(mockAssert.Empty("something"), "Non Empty string is not empty")
|
||||
assert.False(mockAssert.Empty(errors.New("something")), "Non nil object is not empty")
|
||||
assert.False(mockAssert.Empty([]string{"something"}), "Non empty string array is not empty")
|
||||
assert.False(mockAssert.Empty(1), "Non-zero int value is not empty")
|
||||
assert.False(mockAssert.Empty(true), "True value is not empty")
|
||||
|
||||
}
|
||||
|
||||
func TestNotEmptyWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
assert.False(mockAssert.NotEmpty(""), "Empty string is empty")
|
||||
assert.False(mockAssert.NotEmpty(nil), "Nil is empty")
|
||||
assert.False(mockAssert.NotEmpty([]string{}), "Empty string array is empty")
|
||||
assert.False(mockAssert.NotEmpty(0), "Zero int value is empty")
|
||||
assert.False(mockAssert.NotEmpty(false), "False value is empty")
|
||||
|
||||
assert.True(mockAssert.NotEmpty("something"), "Non Empty string is not empty")
|
||||
assert.True(mockAssert.NotEmpty(errors.New("something")), "Non nil object is not empty")
|
||||
assert.True(mockAssert.NotEmpty([]string{"something"}), "Non empty string array is not empty")
|
||||
assert.True(mockAssert.NotEmpty(1), "Non-zero int value is not empty")
|
||||
assert.True(mockAssert.NotEmpty(true), "True value is not empty")
|
||||
|
||||
}
|
||||
|
||||
func TestLenWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
assert.False(mockAssert.Len(nil, 0), "nil does not have length")
|
||||
assert.False(mockAssert.Len(0, 0), "int does not have length")
|
||||
assert.False(mockAssert.Len(true, 0), "true does not have length")
|
||||
assert.False(mockAssert.Len(false, 0), "false does not have length")
|
||||
assert.False(mockAssert.Len('A', 0), "Rune does not have length")
|
||||
assert.False(mockAssert.Len(struct{}{}, 0), "Struct does not have length")
|
||||
|
||||
ch := make(chan int, 5)
|
||||
ch <- 1
|
||||
ch <- 2
|
||||
ch <- 3
|
||||
|
||||
cases := []struct {
|
||||
v interface{}
|
||||
l int
|
||||
}{
|
||||
{[]int{1, 2, 3}, 3},
|
||||
{[...]int{1, 2, 3}, 3},
|
||||
{"ABC", 3},
|
||||
{map[int]int{1: 2, 2: 4, 3: 6}, 3},
|
||||
{ch, 3},
|
||||
|
||||
{[]int{}, 0},
|
||||
{map[int]int{}, 0},
|
||||
{make(chan int), 0},
|
||||
|
||||
{[]int(nil), 0},
|
||||
{map[int]int(nil), 0},
|
||||
{(chan int)(nil), 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
assert.True(mockAssert.Len(c.v, c.l), "%#v have %d items", c.v, c.l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithinDurationWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
a := time.Now()
|
||||
b := a.Add(10 * time.Second)
|
||||
|
||||
assert.True(mockAssert.WithinDuration(a, b, 10*time.Second), "A 10s difference is within a 10s time difference")
|
||||
assert.True(mockAssert.WithinDuration(b, a, 10*time.Second), "A 10s difference is within a 10s time difference")
|
||||
|
||||
assert.False(mockAssert.WithinDuration(a, b, 9*time.Second), "A 10s difference is not within a 9s time difference")
|
||||
assert.False(mockAssert.WithinDuration(b, a, 9*time.Second), "A 10s difference is not within a 9s time difference")
|
||||
|
||||
assert.False(mockAssert.WithinDuration(a, b, -9*time.Second), "A 10s difference is not within a 9s time difference")
|
||||
assert.False(mockAssert.WithinDuration(b, a, -9*time.Second), "A 10s difference is not within a 9s time difference")
|
||||
|
||||
assert.False(mockAssert.WithinDuration(a, b, -11*time.Second), "A 10s difference is not within a 9s time difference")
|
||||
assert.False(mockAssert.WithinDuration(b, a, -11*time.Second), "A 10s difference is not within a 9s time difference")
|
||||
}
|
||||
|
||||
func TestInDeltaWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
True(t, assert.InDelta(1.001, 1, 0.01), "|1.001 - 1| <= 0.01")
|
||||
True(t, assert.InDelta(1, 1.001, 0.01), "|1 - 1.001| <= 0.01")
|
||||
True(t, assert.InDelta(1, 2, 1), "|1 - 2| <= 1")
|
||||
False(t, assert.InDelta(1, 2, 0.5), "Expected |1 - 2| <= 0.5 to fail")
|
||||
False(t, assert.InDelta(2, 1, 0.5), "Expected |2 - 1| <= 0.5 to fail")
|
||||
False(t, assert.InDelta("", nil, 1), "Expected non numerals to fail")
|
||||
|
||||
cases := []struct {
|
||||
a, b interface{}
|
||||
delta float64
|
||||
}{
|
||||
{uint8(2), uint8(1), 1},
|
||||
{uint16(2), uint16(1), 1},
|
||||
{uint32(2), uint32(1), 1},
|
||||
{uint64(2), uint64(1), 1},
|
||||
|
||||
{int(2), int(1), 1},
|
||||
{int8(2), int8(1), 1},
|
||||
{int16(2), int16(1), 1},
|
||||
{int32(2), int32(1), 1},
|
||||
{int64(2), int64(1), 1},
|
||||
|
||||
{float32(2), float32(1), 1},
|
||||
{float64(2), float64(1), 1},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
True(t, assert.InDelta(tc.a, tc.b, tc.delta), "Expected |%V - %V| <= %v", tc.a, tc.b, tc.delta)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInEpsilonWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
cases := []struct {
|
||||
a, b interface{}
|
||||
epsilon float64
|
||||
}{
|
||||
{uint8(2), uint16(2), .001},
|
||||
{2.1, 2.2, 0.1},
|
||||
{2.2, 2.1, 0.1},
|
||||
{-2.1, -2.2, 0.1},
|
||||
{-2.2, -2.1, 0.1},
|
||||
{uint64(100), uint8(101), 0.01},
|
||||
{0.1, -0.1, 2},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
True(t, assert.InEpsilon(tc.a, tc.b, tc.epsilon, "Expected %V and %V to have a relative difference of %v", tc.a, tc.b, tc.epsilon))
|
||||
}
|
||||
|
||||
cases = []struct {
|
||||
a, b interface{}
|
||||
epsilon float64
|
||||
}{
|
||||
{uint8(2), int16(-2), .001},
|
||||
{uint64(100), uint8(102), 0.01},
|
||||
{2.1, 2.2, 0.001},
|
||||
{2.2, 2.1, 0.001},
|
||||
{2.1, -2.2, 1},
|
||||
{2.1, "bla-bla", 0},
|
||||
{0.1, -0.1, 1.99},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
False(t, assert.InEpsilon(tc.a, tc.b, tc.epsilon, "Expected %V and %V to have a relative difference of %v", tc.a, tc.b, tc.epsilon))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexpWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
|
||||
cases := []struct {
|
||||
rx, str string
|
||||
}{
|
||||
{"^start", "start of the line"},
|
||||
{"end$", "in the end"},
|
||||
{"[0-9]{3}[.-]?[0-9]{2}[.-]?[0-9]{2}", "My phone number is 650.12.34"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
True(t, assert.Regexp(tc.rx, tc.str))
|
||||
True(t, assert.Regexp(regexp.MustCompile(tc.rx), tc.str))
|
||||
False(t, assert.NotRegexp(tc.rx, tc.str))
|
||||
False(t, assert.NotRegexp(regexp.MustCompile(tc.rx), tc.str))
|
||||
}
|
||||
|
||||
cases = []struct {
|
||||
rx, str string
|
||||
}{
|
||||
{"^asdfastart", "Not the start of the line"},
|
||||
{"end$", "in the end."},
|
||||
{"[0-9]{3}[.-]?[0-9]{2}[.-]?[0-9]{2}", "My phone number is 650.12a.34"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
False(t, assert.Regexp(tc.rx, tc.str), "Expected \"%s\" to not match \"%s\"", tc.rx, tc.str)
|
||||
False(t, assert.Regexp(regexp.MustCompile(tc.rx), tc.str))
|
||||
True(t, assert.NotRegexp(tc.rx, tc.str))
|
||||
True(t, assert.NotRegexp(regexp.MustCompile(tc.rx), tc.str))
|
||||
}
|
||||
}
|
||||
|
||||
func TestZeroWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
for _, test := range zeros {
|
||||
assert.True(mockAssert.Zero(test), "Zero should return true for %v", test)
|
||||
}
|
||||
|
||||
for _, test := range nonZeros {
|
||||
assert.False(mockAssert.Zero(test), "Zero should return false for %v", test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotZeroWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
for _, test := range zeros {
|
||||
assert.False(mockAssert.NotZero(test), "Zero should return true for %v", test)
|
||||
}
|
||||
|
||||
for _, test := range nonZeros {
|
||||
assert.True(mockAssert.NotZero(test), "Zero should return false for %v", test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_EqualSONString(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if !assert.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`) {
|
||||
t.Error("JSONEq should return true")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_EquivalentButNotEqual(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if !assert.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) {
|
||||
t.Error("JSONEq should return true")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_HashOfArraysAndHashes(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if !assert.JSONEq("{\r\n\t\"numeric\": 1.5,\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]],\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\"\r\n}",
|
||||
"{\r\n\t\"numeric\": 1.5,\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\",\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]]\r\n}") {
|
||||
t.Error("JSONEq should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_Array(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if !assert.JSONEq(`["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`) {
|
||||
t.Error("JSONEq should return true")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_HashAndArrayNotEquivalent(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.JSONEq(`["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`) {
|
||||
t.Error("JSONEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_HashesNotEquivalent(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.JSONEq(`{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) {
|
||||
t.Error("JSONEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_ActualIsNotJSON(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.JSONEq(`{"foo": "bar"}`, "Not JSON") {
|
||||
t.Error("JSONEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_ExpectedIsNotJSON(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.JSONEq("Not JSON", `{"foo": "bar", "hello": "world"}`) {
|
||||
t.Error("JSONEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_ExpectedAndActualNotJSON(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.JSONEq("Not JSON", "Not JSON") {
|
||||
t.Error("JSONEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_ArraysOfDifferentOrder(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.JSONEq(`["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`) {
|
||||
t.Error("JSONEq should return false")
|
||||
}
|
||||
}
|
||||
127
vendor/github.com/stretchr/testify/assert/http_assertions.go
generated
vendored
Normal file
127
vendor/github.com/stretchr/testify/assert/http_assertions.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
package assert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// httpCode is a helper that returns HTTP code of the response. It returns -1 and
|
||||
// an error if building a new request fails.
|
||||
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
handler(w, req)
|
||||
return w.Code, nil
|
||||
}
|
||||
|
||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
|
||||
if !isSuccessCode {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return isSuccessCode
|
||||
}
|
||||
|
||||
// HTTPRedirect asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
|
||||
if !isRedirectCode {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return isRedirectCode
|
||||
}
|
||||
|
||||
// HTTPError asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
isErrorCode := code >= http.StatusBadRequest
|
||||
if !isErrorCode {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return isErrorCode
|
||||
}
|
||||
|
||||
// HTTPBody is a helper that returns HTTP body of the response. It returns
|
||||
// empty string if building a new request fails.
|
||||
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
handler(w, req)
|
||||
return w.Body.String()
|
||||
}
|
||||
|
||||
// HTTPBodyContains asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
body := HTTPBody(handler, method, url, values)
|
||||
|
||||
contains := strings.Contains(body, fmt.Sprint(str))
|
||||
if !contains {
|
||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
|
||||
}
|
||||
|
||||
return contains
|
||||
}
|
||||
|
||||
// HTTPBodyNotContains asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
body := HTTPBody(handler, method, url, values)
|
||||
|
||||
contains := strings.Contains(body, fmt.Sprint(str))
|
||||
if contains {
|
||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
|
||||
}
|
||||
|
||||
return !contains
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user