mirror of
https://github.com/gohugoio/hugo.git
synced 2025-04-26 21:51:02 +03:00
Add a "deploy" command
This commit is contained in:
parent
ad5703a917
commit
c7165589b3
10 changed files with 1725 additions and 12 deletions
308
deploy/deploy_test.go
Normal file
308
deploy/deploy_test.go
Normal file
|
@ -0,0 +1,308 @@
|
|||
// Copyright 2019 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/md5"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/spf13/afero"
|
||||
"gocloud.dev/blob"
|
||||
)
|
||||
|
||||
func TestDeploy_FindDiffs(t *testing.T) {
|
||||
hash1 := []byte("hash 1")
|
||||
hash2 := []byte("hash 2")
|
||||
makeLocal := func(path string, size int64, hash []byte) *localFile {
|
||||
return &localFile{Path: path, UploadSize: size, md5: hash}
|
||||
}
|
||||
makeRemote := func(path string, size int64, hash []byte) *blob.ListObject {
|
||||
return &blob.ListObject{Key: path, Size: size, MD5: hash}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Description string
|
||||
Local []*localFile
|
||||
Remote []*blob.ListObject
|
||||
Force bool
|
||||
WantUpdates []*fileToUpload
|
||||
WantDeletes []string
|
||||
}{
|
||||
{
|
||||
Description: "empty -> no diffs",
|
||||
},
|
||||
{
|
||||
Description: "local == remote -> no diffs",
|
||||
Local: []*localFile{
|
||||
makeLocal("aaa", 1, hash1),
|
||||
makeLocal("bbb", 2, hash1),
|
||||
makeLocal("ccc", 3, hash2),
|
||||
},
|
||||
Remote: []*blob.ListObject{
|
||||
makeRemote("aaa", 1, hash1),
|
||||
makeRemote("bbb", 2, hash1),
|
||||
makeRemote("ccc", 3, hash2),
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "local == remote with force flag true -> diffs",
|
||||
Local: []*localFile{
|
||||
makeLocal("aaa", 1, hash1),
|
||||
makeLocal("bbb", 2, hash1),
|
||||
makeLocal("ccc", 3, hash2),
|
||||
},
|
||||
Remote: []*blob.ListObject{
|
||||
makeRemote("aaa", 1, hash1),
|
||||
makeRemote("bbb", 2, hash1),
|
||||
makeRemote("ccc", 3, hash2),
|
||||
},
|
||||
Force: true,
|
||||
WantUpdates: []*fileToUpload{
|
||||
{makeLocal("aaa", 1, nil), reasonForce},
|
||||
{makeLocal("bbb", 2, nil), reasonForce},
|
||||
{makeLocal("ccc", 3, nil), reasonForce},
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "local == remote with route.Force true -> diffs",
|
||||
Local: []*localFile{
|
||||
{Path: "aaa", UploadSize: 1, matcher: &matcher{Force: true}, md5: hash1},
|
||||
makeLocal("bbb", 2, hash1),
|
||||
},
|
||||
Remote: []*blob.ListObject{
|
||||
makeRemote("aaa", 1, hash1),
|
||||
makeRemote("bbb", 2, hash1),
|
||||
},
|
||||
WantUpdates: []*fileToUpload{
|
||||
{makeLocal("aaa", 1, nil), reasonForce},
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "extra local file -> upload",
|
||||
Local: []*localFile{
|
||||
makeLocal("aaa", 1, hash1),
|
||||
makeLocal("bbb", 2, hash2),
|
||||
},
|
||||
Remote: []*blob.ListObject{
|
||||
makeRemote("aaa", 1, hash1),
|
||||
},
|
||||
WantUpdates: []*fileToUpload{
|
||||
{makeLocal("bbb", 2, nil), reasonNotFound},
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "extra remote file -> delete",
|
||||
Local: []*localFile{
|
||||
makeLocal("aaa", 1, hash1),
|
||||
},
|
||||
Remote: []*blob.ListObject{
|
||||
makeRemote("aaa", 1, hash1),
|
||||
makeRemote("bbb", 2, hash2),
|
||||
},
|
||||
WantDeletes: []string{"bbb"},
|
||||
},
|
||||
{
|
||||
Description: "diffs in size or md5 -> upload",
|
||||
Local: []*localFile{
|
||||
makeLocal("aaa", 1, hash1),
|
||||
makeLocal("bbb", 2, hash1),
|
||||
makeLocal("ccc", 1, hash2),
|
||||
},
|
||||
Remote: []*blob.ListObject{
|
||||
makeRemote("aaa", 1, nil),
|
||||
makeRemote("bbb", 1, hash1),
|
||||
makeRemote("ccc", 1, hash1),
|
||||
},
|
||||
WantUpdates: []*fileToUpload{
|
||||
{makeLocal("aaa", 1, nil), reasonMD5Missing},
|
||||
{makeLocal("bbb", 2, nil), reasonSize},
|
||||
{makeLocal("ccc", 1, nil), reasonMD5Differs},
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "mix of updates and deletes",
|
||||
Local: []*localFile{
|
||||
makeLocal("same", 1, hash1),
|
||||
makeLocal("updated", 2, hash1),
|
||||
makeLocal("updated2", 1, hash2),
|
||||
makeLocal("new", 1, hash1),
|
||||
makeLocal("new2", 2, hash2),
|
||||
},
|
||||
Remote: []*blob.ListObject{
|
||||
makeRemote("same", 1, hash1),
|
||||
makeRemote("updated", 1, hash1),
|
||||
makeRemote("updated2", 1, hash1),
|
||||
makeRemote("stale", 1, hash1),
|
||||
makeRemote("stale2", 1, hash1),
|
||||
},
|
||||
WantUpdates: []*fileToUpload{
|
||||
{makeLocal("new", 1, nil), reasonNotFound},
|
||||
{makeLocal("new2", 2, nil), reasonNotFound},
|
||||
{makeLocal("updated", 2, nil), reasonSize},
|
||||
{makeLocal("updated2", 1, nil), reasonMD5Differs},
|
||||
},
|
||||
WantDeletes: []string{"stale", "stale2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.Description, func(t *testing.T) {
|
||||
local := map[string]*localFile{}
|
||||
for _, l := range tc.Local {
|
||||
local[l.Path] = l
|
||||
}
|
||||
remote := map[string]*blob.ListObject{}
|
||||
for _, r := range tc.Remote {
|
||||
remote[r.Key] = r
|
||||
}
|
||||
gotUpdates, gotDeletes := findDiffs(local, remote, tc.Force)
|
||||
sort.Slice(gotUpdates, func(i, j int) bool { return gotUpdates[i].Local.Path < gotUpdates[j].Local.Path })
|
||||
sort.Slice(gotDeletes, func(i, j int) bool { return gotDeletes[i] < gotDeletes[j] })
|
||||
if diff := cmp.Diff(gotUpdates, tc.WantUpdates, cmpopts.IgnoreUnexported(localFile{})); diff != "" {
|
||||
t.Errorf("updates differ:\n%s", diff)
|
||||
|
||||
}
|
||||
if diff := cmp.Diff(gotDeletes, tc.WantDeletes); diff != "" {
|
||||
t.Errorf("deletes differ:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploy_LocalFile(t *testing.T) {
|
||||
const (
|
||||
content = "hello world!"
|
||||
)
|
||||
contentBytes := []byte(content)
|
||||
contentLen := int64(len(contentBytes))
|
||||
contentMD5 := md5.Sum(contentBytes)
|
||||
var buf bytes.Buffer
|
||||
gz := gzip.NewWriter(&buf)
|
||||
if _, err := gz.Write(contentBytes); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gz.Close()
|
||||
gzBytes := buf.Bytes()
|
||||
gzLen := int64(len(gzBytes))
|
||||
gzMD5 := md5.Sum(gzBytes)
|
||||
|
||||
tests := []struct {
|
||||
Description string
|
||||
Path string
|
||||
Matcher *matcher
|
||||
WantContent []byte
|
||||
WantSize int64
|
||||
WantMD5 []byte
|
||||
WantContentType string // empty string is always OK, since content type detection is OS-specific
|
||||
WantCacheControl string
|
||||
WantContentEncoding string
|
||||
}{
|
||||
{
|
||||
Description: "file with no suffix",
|
||||
Path: "foo",
|
||||
WantContent: contentBytes,
|
||||
WantSize: contentLen,
|
||||
WantMD5: contentMD5[:],
|
||||
},
|
||||
{
|
||||
Description: "file with .txt suffix",
|
||||
Path: "foo.txt",
|
||||
WantContent: contentBytes,
|
||||
WantSize: contentLen,
|
||||
WantMD5: contentMD5[:],
|
||||
},
|
||||
{
|
||||
Description: "CacheControl from matcher",
|
||||
Path: "foo.txt",
|
||||
Matcher: &matcher{CacheControl: "max-age=630720000"},
|
||||
WantContent: contentBytes,
|
||||
WantSize: contentLen,
|
||||
WantMD5: contentMD5[:],
|
||||
WantCacheControl: "max-age=630720000",
|
||||
},
|
||||
{
|
||||
Description: "ContentEncoding from matcher",
|
||||
Path: "foo.txt",
|
||||
Matcher: &matcher{ContentEncoding: "foobar"},
|
||||
WantContent: contentBytes,
|
||||
WantSize: contentLen,
|
||||
WantMD5: contentMD5[:],
|
||||
WantContentEncoding: "foobar",
|
||||
},
|
||||
{
|
||||
Description: "ContentType from matcher",
|
||||
Path: "foo.txt",
|
||||
Matcher: &matcher{ContentType: "foo/bar"},
|
||||
WantContent: contentBytes,
|
||||
WantSize: contentLen,
|
||||
WantMD5: contentMD5[:],
|
||||
WantContentType: "foo/bar",
|
||||
},
|
||||
{
|
||||
Description: "gzipped content",
|
||||
Path: "foo.txt",
|
||||
Matcher: &matcher{Gzip: true},
|
||||
WantContent: gzBytes,
|
||||
WantSize: gzLen,
|
||||
WantMD5: gzMD5[:],
|
||||
WantContentEncoding: "gzip",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.Description, func(t *testing.T) {
|
||||
fs := new(afero.MemMapFs)
|
||||
if err := afero.WriteFile(fs, tc.Path, []byte(content), os.ModePerm); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lf, err := newLocalFile(fs, tc.Path, tc.Matcher)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := lf.UploadSize; got != tc.WantSize {
|
||||
t.Errorf("got size %d want %d", got, tc.WantSize)
|
||||
}
|
||||
if got := lf.MD5(); !bytes.Equal(got, tc.WantMD5) {
|
||||
t.Errorf("got MD5 %x want %x", got, tc.WantMD5)
|
||||
}
|
||||
if got := lf.CacheControl(); got != tc.WantCacheControl {
|
||||
t.Errorf("got CacheControl %q want %q", got, tc.WantCacheControl)
|
||||
}
|
||||
if got := lf.ContentEncoding(); got != tc.WantContentEncoding {
|
||||
t.Errorf("got ContentEncoding %q want %q", got, tc.WantContentEncoding)
|
||||
}
|
||||
if tc.WantContentType != "" {
|
||||
if got := lf.ContentType(); got != tc.WantContentType {
|
||||
t.Errorf("got ContentType %q want %q", got, tc.WantContentType)
|
||||
}
|
||||
}
|
||||
// Verify the content reader last to ensure the
|
||||
// previous operations don't interfere with it.
|
||||
gotContent, err := ioutil.ReadAll(lf.UploadContentReader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(gotContent, tc.WantContent) {
|
||||
t.Errorf("got content %q want %q", string(gotContent), string(tc.WantContent))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue