Commit cc958947 authored by Matt Holt's avatar Matt Holt

Merge pull request #83 from abiosoft/master

Git: Minor fixes. Refactor. Added tests.
parents bba2d63d f44cd5d7
...@@ -96,6 +96,8 @@ func gitParse(c *Controller) (*git.Repo, error) { ...@@ -96,6 +96,8 @@ func gitParse(c *Controller) (*git.Repo, error) {
return nil, c.ArgErr() return nil, c.ArgErr()
} }
repo.Then = strings.Join(thenArgs, " ") repo.Then = strings.Join(thenArgs, " ")
default:
return nil, c.ArgErr()
} }
} }
} }
...@@ -124,8 +126,8 @@ func gitParse(c *Controller) (*git.Repo, error) { ...@@ -124,8 +126,8 @@ func gitParse(c *Controller) (*git.Repo, error) {
return nil, err return nil, err
} }
// validate git availability in PATH // validate git requirements
if err = git.InitGit(); err != nil { if err = git.Init(); err != nil {
return nil, err return nil, err
} }
...@@ -153,19 +155,39 @@ func sanitizeHttp(repoUrl string) (string, string, error) { ...@@ -153,19 +155,39 @@ func sanitizeHttp(repoUrl string) (string, string, error) {
} }
repoUrl = "https://" + url.Host + url.Path repoUrl = "https://" + url.Host + url.Path
// add .git suffix if missing
if !strings.HasSuffix(repoUrl, ".git") {
repoUrl += ".git"
}
return repoUrl, url.Host, nil return repoUrl, url.Host, nil
} }
// sanitizeGit cleans up repository url and validate ssh format. // sanitizeGit cleans up repository url and converts to ssh format for private
// repositories if required.
// Returns sanitized url, hostName (e.g. github.com, bitbucket.com) // Returns sanitized url, hostName (e.g. github.com, bitbucket.com)
// and possible error // and possible error
func sanitizeGit(repoUrl string) (string, string, error) { func sanitizeGit(repoUrl string) (string, string, error) {
repoUrl = strings.TrimSpace(repoUrl) repoUrl = strings.TrimSpace(repoUrl)
// check if valid ssh format
if !strings.HasPrefix(repoUrl, "git@") || strings.Index(repoUrl, ":") < len("git@a:") { if !strings.HasPrefix(repoUrl, "git@") || strings.Index(repoUrl, ":") < len("git@a:") {
return "", "", fmt.Errorf("Invalid git url %s", repoUrl) // check if valid http format and convert to ssh
if url, err := url.Parse(repoUrl); err == nil && strings.HasPrefix(url.Scheme, "http") {
repoUrl = fmt.Sprintf("git@%v:%v", url.Host, url.Path[1:])
} else {
return "", "", fmt.Errorf("Invalid git url %s", repoUrl)
}
} }
hostUrl := repoUrl[len("git@"):] hostUrl := repoUrl[len("git@"):]
i := strings.Index(hostUrl, ":") i := strings.Index(hostUrl, ":")
host := hostUrl[:i] host := hostUrl[:i]
// add .git suffix if missing
if !strings.HasSuffix(repoUrl, ".git") {
repoUrl += ".git"
}
return repoUrl, host, nil return repoUrl, host, nil
} }
package setup
import (
"testing"
"time"
"github.com/mholt/caddy/middleware/git"
"github.com/mholt/caddy/middleware/git/gittest"
)
// init sets the OS used to fakeOS
func init() {
git.SetOS(gittest.FakeOS)
}
func TestGit(t *testing.T) {
c := newTestController(`git git@github.com:mholt/caddy.git`)
mid, err := Git(c)
if err != nil {
t.Errorf("Expected no errors, but got: %v", err)
}
if mid != nil {
t.Fatal("Git middleware is a background service and expected to be nil.")
}
}
func TestGitParse(t *testing.T) {
tests := []struct {
input string
shouldErr bool
expected *git.Repo
}{
{`git git@github.com:user/repo`, false, &git.Repo{
Url: "https://github.com/user/repo.git",
}},
{`git github.com/user/repo`, false, &git.Repo{
Url: "https://github.com/user/repo.git",
}},
{`git git@github.com/user/repo`, true, nil},
{`git http://github.com/user/repo`, false, &git.Repo{
Url: "https://github.com/user/repo.git",
}},
{`git https://github.com/user/repo`, false, &git.Repo{
Url: "https://github.com/user/repo.git",
}},
{`git http://github.com/user/repo {
key ~/.key
}`, false, &git.Repo{
KeyPath: "~/.key",
Url: "git@github.com:user/repo.git",
}},
{`git git@github.com:user/repo {
key ~/.key
}`, false, &git.Repo{
KeyPath: "~/.key",
Url: "git@github.com:user/repo.git",
}},
{`git `, true, nil},
{`git {
}`, true, nil},
{`git {
repo git@github.com:user/repo.git`, true, nil},
{`git {
repo git@github.com:user/repo
key ~/.key
}`, false, &git.Repo{
KeyPath: "~/.key",
Url: "git@github.com:user/repo.git",
}},
{`git {
repo git@github.com:user/repo
key ~/.key
interval 600
}`, false, &git.Repo{
KeyPath: "~/.key",
Url: "git@github.com:user/repo.git",
Interval: time.Second * 600,
}},
{`git {
repo git@github.com:user/repo
branch dev
}`, false, &git.Repo{
Branch: "dev",
Url: "https://github.com/user/repo.git",
}},
{`git {
key ~/.key
}`, true, nil},
{`git {
repo git@github.com:user/repo
key ~/.key
then echo hello world
}`, false, &git.Repo{
KeyPath: "~/.key",
Url: "git@github.com:user/repo.git",
Then: "echo hello world",
}},
}
for i, test := range tests {
c := newTestController(test.input)
repo, err := gitParse(c)
if !test.shouldErr && err != nil {
t.Errorf("Test %v should not error but found %v", i, err)
continue
}
if test.shouldErr && err == nil {
t.Errorf("Test %v should error but found nil", i)
continue
}
if !reposEqual(test.expected, repo) {
t.Errorf("Test %v expects %v but found %v", i, test.expected, repo)
}
}
}
func reposEqual(expected, repo *git.Repo) bool {
if expected == nil {
return repo == nil
}
if expected.Branch != "" && expected.Branch != repo.Branch {
return false
}
if expected.Host != "" && expected.Host != repo.Host {
return false
}
if expected.Interval != 0 && expected.Interval != repo.Interval {
return false
}
if expected.KeyPath != "" && expected.KeyPath != repo.KeyPath {
return false
}
if expected.Path != "" && expected.Path != repo.Path {
return false
}
if expected.Then != "" && expected.Then != repo.Then {
return false
}
if expected.Url != "" && expected.Url != repo.Url {
return false
}
return true
}
...@@ -3,15 +3,14 @@ package git ...@@ -3,15 +3,14 @@ package git
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"os" "os"
"os/exec"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/git/gitos"
) )
// DefaultInterval is the minimum interval to delay before // DefaultInterval is the minimum interval to delay before
...@@ -24,8 +23,11 @@ const numRetries = 3 ...@@ -24,8 +23,11 @@ const numRetries = 3
// gitBinary holds the absolute path to git executable // gitBinary holds the absolute path to git executable
var gitBinary string var gitBinary string
// shell holds the shell to be used. Either sh or bash.
var shell string
// initMutex prevents parallel attempt to validate // initMutex prevents parallel attempt to validate
// git availability in PATH // git requirements.
var initMutex sync.Mutex = sync.Mutex{} var initMutex sync.Mutex = sync.Mutex{}
// Logger is used to log errors; if nil, the default log.Logger is used. // Logger is used to log errors; if nil, the default log.Logger is used.
...@@ -120,20 +122,20 @@ func (r *Repo) pull() error { ...@@ -120,20 +122,20 @@ func (r *Repo) pull() error {
// pullWithKey is used for private repositories and requires an ssh key. // pullWithKey is used for private repositories and requires an ssh key.
// Note: currently only limited to Linux and OSX. // Note: currently only limited to Linux and OSX.
func (r *Repo) pullWithKey(params []string) error { func (r *Repo) pullWithKey(params []string) error {
var gitSsh, script *os.File var gitSsh, script gitos.File
// ensure temporary files deleted after usage // ensure temporary files deleted after usage
defer func() { defer func() {
if gitSsh != nil { if gitSsh != nil {
os.Remove(gitSsh.Name()) gos.Remove(gitSsh.Name())
} }
if script != nil { if script != nil {
os.Remove(script.Name()) gos.Remove(script.Name())
} }
}() }()
var err error var err error
// write git.sh script to temp file // write git.sh script to temp file
gitSsh, err = writeScriptFile(gitWrapperScript(gitBinary)) gitSsh, err = writeScriptFile(gitWrapperScript())
if err != nil { if err != nil {
return err return err
} }
...@@ -163,9 +165,9 @@ func (r *Repo) pullWithKey(params []string) error { ...@@ -163,9 +165,9 @@ func (r *Repo) pullWithKey(params []string) error {
func (r *Repo) Prepare() error { func (r *Repo) Prepare() error {
// check if directory exists or is empty // check if directory exists or is empty
// if not, create directory // if not, create directory
fs, err := ioutil.ReadDir(r.Path) fs, err := gos.ReadDir(r.Path)
if err != nil || len(fs) == 0 { if err != nil || len(fs) == 0 {
return os.MkdirAll(r.Path, os.FileMode(0755)) return gos.MkdirAll(r.Path, os.FileMode(0755))
} }
// validate git repo // validate git repo
...@@ -180,9 +182,15 @@ func (r *Repo) Prepare() error { ...@@ -180,9 +182,15 @@ func (r *Repo) Prepare() error {
if isGit { if isGit {
// check if same repository // check if same repository
var repoUrl string var repoUrl string
if repoUrl, err = r.getRepoUrl(); err == nil && repoUrl == r.Url { if repoUrl, err = r.getRepoUrl(); err == nil {
r.pulled = true // add .git suffix if missing for adequate comparison.
return nil if !strings.HasSuffix(repoUrl, ".git") {
repoUrl += ".git"
}
if repoUrl == r.Url {
r.pulled = true
return nil
}
} }
if err != nil { if err != nil {
return fmt.Errorf("Cannot retrieve repo url for %v Error: %v", r.Path, err) return fmt.Errorf("Cannot retrieve repo url for %v Error: %v", r.Path, err)
...@@ -205,7 +213,7 @@ func (r *Repo) getMostRecentCommit() (string, error) { ...@@ -205,7 +213,7 @@ func (r *Repo) getMostRecentCommit() (string, error) {
// getRepoUrl retrieves remote origin url for the git repository at path // getRepoUrl retrieves remote origin url for the git repository at path
func (r *Repo) getRepoUrl() (string, error) { func (r *Repo) getRepoUrl() (string, error) {
_, err := os.Stat(r.Path) _, err := gos.Stat(r.Path)
if err != nil { if err != nil {
return "", err return "", err
} }
...@@ -230,9 +238,9 @@ func (r *Repo) postPullCommand() error { ...@@ -230,9 +238,9 @@ func (r *Repo) postPullCommand() error {
return err return err
} }
// InitGit validates git installation and locates the git executable // Init validates git installation, locates the git executable
// binary in PATH // binary in PATH and check for available shell to use.
func InitGit() error { func Init() error {
// prevent concurrent call // prevent concurrent call
initMutex.Lock() initMutex.Lock()
defer initMutex.Unlock() defer initMutex.Unlock()
...@@ -245,18 +253,30 @@ func InitGit() error { ...@@ -245,18 +253,30 @@ func InitGit() error {
// locate git binary in path // locate git binary in path
var err error var err error
gitBinary, err = exec.LookPath("git") if gitBinary, err = gos.LookPath("git"); err != nil {
return err return fmt.Errorf("Git middleware requires git installed. Cannot find git binary in PATH")
}
// locate bash in PATH. If not found, fallback to sh.
// If neither is found, return error.
shell = "bash"
if _, err = gos.LookPath("bash"); err != nil {
shell = "sh"
if _, err = gos.LookPath("sh"); err != nil {
return fmt.Errorf("Git middleware requires either bash or sh.")
}
}
return nil
} }
// runCmd is a helper function to run commands. // runCmd is a helper function to run commands.
// It runs command with args from directory at dir. // It runs command with args from directory at dir.
// The executed process outputs to os.Stderr // The executed process outputs to os.Stderr
func runCmd(command string, args []string, dir string) error { func runCmd(command string, args []string, dir string) error {
cmd := exec.Command(command, args...) cmd := gos.Command(command, args...)
cmd.Stderr = os.Stderr cmd.Stdout(os.Stderr)
cmd.Stdout = os.Stderr cmd.Stderr(os.Stderr)
cmd.Dir = dir cmd.Dir(dir)
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return err return err
} }
...@@ -267,8 +287,8 @@ func runCmd(command string, args []string, dir string) error { ...@@ -267,8 +287,8 @@ func runCmd(command string, args []string, dir string) error {
// It runs command with args from directory at dir. // It runs command with args from directory at dir.
// If successful, returns output and nil error // If successful, returns output and nil error
func runCmdOutput(command string, args []string, dir string) (string, error) { func runCmdOutput(command string, args []string, dir string) (string, error) {
cmd := exec.Command(command, args...) cmd := gos.Command(command, args...)
cmd.Dir = dir cmd.Dir(dir)
var err error var err error
if output, err := cmd.Output(); err == nil { if output, err := cmd.Output(); err == nil {
return string(bytes.TrimSpace(output)), nil return string(bytes.TrimSpace(output)), nil
...@@ -279,8 +299,8 @@ func runCmdOutput(command string, args []string, dir string) (string, error) { ...@@ -279,8 +299,8 @@ func runCmdOutput(command string, args []string, dir string) (string, error) {
// writeScriptFile writes content to a temporary file. // writeScriptFile writes content to a temporary file.
// It changes the temporary file mode to executable and // It changes the temporary file mode to executable and
// closes it to prepare it for execution. // closes it to prepare it for execution.
func writeScriptFile(content []byte) (file *os.File, err error) { func writeScriptFile(content []byte) (file gitos.File, err error) {
if file, err = ioutil.TempFile("", "caddy"); err != nil { if file, err = gos.TempFile("", "caddy"); err != nil {
return nil, err return nil, err
} }
if _, err = file.Write(content); err != nil { if _, err = file.Write(content); err != nil {
...@@ -293,8 +313,8 @@ func writeScriptFile(content []byte) (file *os.File, err error) { ...@@ -293,8 +313,8 @@ func writeScriptFile(content []byte) (file *os.File, err error) {
} }
// gitWrapperScript forms content for git.sh script // gitWrapperScript forms content for git.sh script
var gitWrapperScript = func(gitBinary string) []byte { func gitWrapperScript() []byte {
return []byte(fmt.Sprintf(`#!/bin/bash return []byte(fmt.Sprintf(`#!/bin/%v
# The MIT License (MIT) # The MIT License (MIT)
# Copyright (c) 2013 Alvin Abad # Copyright (c) 2013 Alvin Abad
...@@ -323,17 +343,17 @@ fi ...@@ -323,17 +343,17 @@ fi
# Run the git command # Run the git command
%v "$@" %v "$@"
`, gitBinary)) `, shell, gitBinary))
} }
// bashScript forms content of bash script to clone or update a repo using ssh // bashScript forms content of bash script to clone or update a repo using ssh
var bashScript = func(gitShPath string, repo *Repo, params []string) []byte { func bashScript(gitShPath string, repo *Repo, params []string) []byte {
return []byte(fmt.Sprintf(`#!/bin/bash return []byte(fmt.Sprintf(`#!/bin/%v
mkdir -p ~/.ssh; mkdir -p ~/.ssh;
touch ~/.ssh/known_hosts; touch ~/.ssh/known_hosts;
ssh-keyscan -t rsa,dsa %v 2>&1 | sort -u - ~/.ssh/known_hosts > ~/.ssh/tmp_hosts; ssh-keyscan -t rsa,dsa %v 2>&1 | sort -u - ~/.ssh/known_hosts > ~/.ssh/tmp_hosts;
cat ~/.ssh/tmp_hosts >> ~/.ssh/known_hosts; cat ~/.ssh/tmp_hosts >> ~/.ssh/known_hosts;
%v -i %v %v; %v -i %v %v;
`, repo.Host, gitShPath, repo.KeyPath, strings.Join(params, " "))) `, shell, repo.Host, gitShPath, repo.KeyPath, strings.Join(params, " ")))
} }
package git
import (
"io/ioutil"
"log"
"testing"
"time"
"github.com/mholt/caddy/middleware/git/gittest"
)
// init sets the OS used to fakeOS.
func init() {
SetOS(gittest.FakeOS)
}
func check(t *testing.T, err error) {
if err != nil {
t.Errorf("Error not expected but found %v", err)
}
}
func TestInit(t *testing.T) {
err := Init()
check(t, err)
}
func TestHelpers(t *testing.T) {
f, err := writeScriptFile([]byte("script"))
check(t, err)
var b [6]byte
_, err = f.Read(b[:])
check(t, err)
if string(b[:]) != "script" {
t.Errorf("Expected script found %v", string(b[:]))
}
out, err := runCmdOutput(gitBinary, []string{"-version"}, "")
check(t, err)
if out != gittest.CmdOutput {
t.Errorf("Expected %v found %v", gittest.CmdOutput, out)
}
err = runCmd(gitBinary, []string{"-version"}, "")
check(t, err)
wScript := gitWrapperScript()
if string(wScript) != expectedWrapperScript {
t.Errorf("Expected %v found %v", expectedWrapperScript, string(wScript))
}
f, err = writeScriptFile(wScript)
check(t, err)
repo := &Repo{Host: "github.com", KeyPath: "~/.key"}
script := string(bashScript(f.Name(), repo, []string{"clone", "git@github.com/repo/user"}))
if script != expectedBashScript {
t.Errorf("Expected %v found %v", expectedBashScript, script)
}
}
func TestGit(t *testing.T) {
// prepare
repos := []*Repo{
nil,
&Repo{Path: "gitdir", Url: "success.git"},
}
for _, r := range repos {
repo := createRepo(r)
err := repo.Prepare()
check(t, err)
}
// pull with success
logFile := gittest.Open("file")
Logger = log.New(logFile, "", 0)
tests := []struct {
repo *Repo
output string
}{
{
&Repo{Path: "gitdir", Url: "git@github.com:user/repo.git", KeyPath: "~/.key", Then: "echo Hello"},
`git@github.com:user/repo.git pulled.
Command echo Hello successful.
`,
},
{
&Repo{Path: "gitdir", Url: "https://github.com/user/repo.git", Then: "echo Hello"},
`https://github.com/user/repo.git pulled.
Command echo Hello successful.
`,
},
{
&Repo{Url: "git@github.com:user/repo"},
`git@github.com:user/repo pulled.
`,
},
}
for i, test := range tests {
gittest.CmdOutput = test.repo.Url
test.repo = createRepo(test.repo)
err := test.repo.Prepare()
check(t, err)
err = test.repo.Pull()
check(t, err)
out, err := ioutil.ReadAll(logFile)
check(t, err)
if test.output != string(out) {
t.Errorf("Pull with Success %v: Expected %v found %v", i, test.output, string(out))
}
}
// pull with error
repos = []*Repo{
&Repo{Path: "gitdir", Url: "http://github.com:u/repo.git"},
&Repo{Path: "gitdir", Url: "https://github.com/user/repo.git", Then: "echo Hello"},
&Repo{Path: "gitdir"},
&Repo{Path: "gitdir", KeyPath: ".key"},
}
gittest.CmdOutput = "git@github.com:u1/repo.git"
for i, repo := range repos {
repo = createRepo(repo)
err := repo.Prepare()
if err == nil {
t.Errorf("Pull with Error %v: Error expected but not found %v", i, err)
continue
}
expected := "Another git repo 'git@github.com:u1/repo.git' exists at gitdir"
if expected != err.Error() {
t.Errorf("Pull with Error %v: Expected %v found %v", i, expected, err.Error())
}
}
}
func createRepo(r *Repo) *Repo {
repo := &Repo{
Url: "git@github.com/user/test",
Path: ".",
Host: "github.com",
Branch: "master",
Interval: time.Second * 60,
}
if r == nil {
return repo
}
if r.Branch != "" {
repo.Branch = r.Branch
}
if r.Host != "" {
repo.Branch = r.Branch
}
if r.Interval != 0 {
repo.Interval = r.Interval
}
if r.KeyPath != "" {
repo.KeyPath = r.KeyPath
}
if r.Path != "" {
repo.Path = r.Path
}
if r.Then != "" {
repo.Then = r.Then
}
if r.Url != "" {
repo.Url = r.Url
}
return repo
}
var expectedBashScript = `#!/bin/bash
mkdir -p ~/.ssh;
touch ~/.ssh/known_hosts;
ssh-keyscan -t rsa,dsa github.com 2>&1 | sort -u - ~/.ssh/known_hosts > ~/.ssh/tmp_hosts;
cat ~/.ssh/tmp_hosts >> ~/.ssh/known_hosts;
` + gittest.TempFileName + ` -i ~/.key clone git@github.com/repo/user;
`
var expectedWrapperScript = `#!/bin/bash
# The MIT License (MIT)
# Copyright (c) 2013 Alvin Abad
if [ $# -eq 0 ]; then
echo "Git wrapper script that can specify an ssh-key file
Usage:
git.sh -i ssh-key-file git-command
"
exit 1
fi
# remove temporary file on exit
trap 'rm -f /tmp/.git_ssh.$$' 0
if [ "$1" = "-i" ]; then
SSH_KEY=$2; shift; shift
echo "ssh -i $SSH_KEY \$@" > /tmp/.git_ssh.$$
chmod +x /tmp/.git_ssh.$$
export GIT_SSH=/tmp/.git_ssh.$$
fi
# in case the git command is repeated
[ "$1" = "git" ] && shift
# Run the git command
/usr/bin/git "$@"
`
package gitos
import (
"io"
"io/ioutil"
"os"
"os/exec"
)
// File is an abstraction for file (os.File).
type File interface {
// Name returns the name of the file
Name() string
// Stat returns the FileInfo structure describing file.
Stat() (os.FileInfo, error)
// Close closes the File, rendering it unusable for I/O.
Close() error
// Chmod changes the mode of the file.
Chmod(os.FileMode) error
// Read reads up to len(b) bytes from the File. It returns the number of
// bytes read and an error, if any.
Read([]byte) (int, error)
// Write writes len(b) bytes to the File. It returns the number of bytes
// written and an error, if any.
Write([]byte) (int, error)
}
// Cmd is an abstraction for external commands (os.Cmd).
type Cmd interface {
// Run starts the specified command and waits for it to complete.
Run() error
// Start starts the specified command but does not wait for it to complete.
Start() error
// Wait waits for the command to exit. It must have been started by Start.
Wait() error
// Output runs the command and returns its standard output.
Output() ([]byte, error)
// Dir sets the working directory of the command.
Dir(string)
// Stdin sets the process's standard input.
Stdin(io.Reader)
// Stdout sets the process's standard output.
Stdout(io.Writer)
// Stderr sets the process's standard output.
Stderr(io.Writer)
}
// gitCmd represents external commands executed by git.
type gitCmd struct {
*exec.Cmd
}
// Dir sets the working directory of the command.
func (g *gitCmd) Dir(dir string) {
g.Cmd.Dir = dir
}
// Stdin sets the process's standard input.
func (g *gitCmd) Stdin(stdin io.Reader) {
g.Cmd.Stdin = stdin
}
// Stdout sets the process's standard output.
func (g *gitCmd) Stdout(stdout io.Writer) {
g.Cmd.Stdout = stdout
}
// Stderr sets the process's standard output.
func (g *gitCmd) Stderr(stderr io.Writer) {
g.Cmd.Stderr = stderr
}
// OS is an abstraction for required OS level functions.
type OS interface {
// Command returns the Cmd to execute the named program with the
// given arguments.
Command(string, ...string) Cmd
// Mkdir creates a new directory with the specified name and permission
// bits.
Mkdir(string, os.FileMode) error
// MkdirAll creates a directory named path, along with any necessary
// parents.
MkdirAll(string, os.FileMode) error
// Stat returns a FileInfo describing the named file.
Stat(string) (os.FileInfo, error)
// Remove removes the named file or directory.
Remove(string) error
// ReadDir reads the directory named by dirname and returns a list of
// directory entries.
ReadDir(string) ([]os.FileInfo, error)
// LookPath searches for an executable binary named file in the directories
// named by the PATH environment variable.
LookPath(string) (string, error)
// TempFile creates a new temporary file in the directory dir with a name
// beginning with prefix, opens the file for reading and writing, and
// returns the resulting File.
TempFile(string, string) (File, error)
}
// GitOS is the implementation of OS for git.
type GitOS struct{}
// Mkdir calls os.Mkdir.
func (g GitOS) Mkdir(name string, perm os.FileMode) error {
return os.Mkdir(name, perm)
}
// MkdirAll calls os.MkdirAll.
func (g GitOS) MkdirAll(path string, perm os.FileMode) error {
return os.MkdirAll(path, perm)
}
// Stat calls os.Stat.
func (g GitOS) Stat(name string) (os.FileInfo, error) {
return os.Stat(name)
}
// Remove calls os.Remove.
func (g GitOS) Remove(name string) error {
return os.Remove(name)
}
// LookPath calls exec.LookPath.
func (g GitOS) LookPath(file string) (string, error) {
return exec.LookPath(file)
}
// TempFile calls ioutil.TempFile.
func (g GitOS) TempFile(dir, prefix string) (File, error) {
return ioutil.TempFile(dir, prefix)
}
// ReadDir calls ioutil.ReadDir.
func (g GitOS) ReadDir(dirname string) ([]os.FileInfo, error) {
return ioutil.ReadDir(dirname)
}
// Command calls exec.Command.
func (g GitOS) Command(name string, args ...string) Cmd {
return &gitCmd{exec.Command(name, args...)}
}
// Package gittest is a test package for the git middleware.
// It implements a mock gitos.OS, gitos.Cmd and gitos.File.
package gittest
import (
"io"
"os"
"time"
"github.com/mholt/caddy/middleware/git/gitos"
)
// FakeOS implements a mock gitos.OS, gitos.Cmd and gitos.File.
var FakeOS = fakeOS{}
// CmdOutput is the output of any call to the mocked gitos.Cmd's Output().
var CmdOutput = "success"
// TempFileName is the name of any file returned by mocked gitos.OS's TempFile().
var TempFileName = "tempfile"
// dirs mocks a fake git dir if filename is "gitdir".
var dirs = map[string][]os.FileInfo{
"gitdir": {
fakeInfo{name: ".git", dir: true},
},
}
// Open creates a new mock gitos.File.
func Open(name string) gitos.File {
return &fakeFile{name: name}
}
// fakeFile is a mock gitos.File.
type fakeFile struct {
name string
dir bool
content []byte
info fakeInfo
}
func (f fakeFile) Name() string {
return f.name
}
func (f fakeFile) Stat() (os.FileInfo, error) {
return fakeInfo{name: f.name}, nil
}
func (f fakeFile) Close() error {
return nil
}
func (f fakeFile) Chmod(mode os.FileMode) error {
f.info.mode = mode
return nil
}
func (f *fakeFile) Read(b []byte) (int, error) {
if len(f.content) == 0 {
return 0, io.EOF
}
n := copy(b, f.content)
f.content = f.content[n:]
return n, nil
}
func (f *fakeFile) Write(b []byte) (int, error) {
f.content = append(f.content, b...)
return len(b), nil
}
// fakeCmd is a mock git.Cmd.
type fakeCmd struct{}
func (f fakeCmd) Run() error {
return nil
}
func (f fakeCmd) Start() error {
return nil
}
func (f fakeCmd) Wait() error {
return nil
}
func (f fakeCmd) Output() ([]byte, error) {
return []byte(CmdOutput), nil
}
func (f fakeCmd) Dir(dir string) {}
func (f fakeCmd) Stdin(stdin io.Reader) {}
func (f fakeCmd) Stdout(stdout io.Writer) {}
func (f fakeCmd) Stderr(stderr io.Writer) {}
// fakeInfo is a mock os.FileInfo.
type fakeInfo struct {
name string
dir bool
mode os.FileMode
}
func (f fakeInfo) Name() string {
return f.name
}
func (f fakeInfo) Size() int64 {
return 1024
}
func (f fakeInfo) Mode() os.FileMode {
return f.mode
}
func (f fakeInfo) ModTime() time.Time {
return time.Now().Truncate(time.Hour)
}
func (f fakeInfo) IsDir() bool {
return f.dir
}
func (f fakeInfo) Sys() interface{} {
return nil
}
// fakeOS is a mock git.OS.
type fakeOS struct{}
func (f fakeOS) Mkdir(name string, perm os.FileMode) error {
return nil
}
func (f fakeOS) MkdirAll(path string, perm os.FileMode) error {
return nil
}
func (f fakeOS) Stat(name string) (os.FileInfo, error) {
return fakeInfo{name: name}, nil
}
func (f fakeOS) Remove(name string) error {
return nil
}
func (f fakeOS) LookPath(file string) (string, error) {
return "/usr/bin/" + file, nil
}
func (f fakeOS) TempFile(dir, prefix string) (gitos.File, error) {
return &fakeFile{name: TempFileName, info: fakeInfo{name: TempFileName}}, nil
}
func (f fakeOS) ReadDir(dirname string) ([]os.FileInfo, error) {
if f, ok := dirs[dirname]; ok {
return f, nil
}
return nil, nil
}
func (f fakeOS) Command(name string, args ...string) gitos.Cmd {
return fakeCmd{}
}
package git
import "github.com/mholt/caddy/middleware/git/gitos"
// gos is the OS used by git.
var gos gitos.OS = gitos.GitOS{}
// SetOS sets the OS to be used. Intended to be used for tests
// to abstract OS level git actions.
func SetOS(os gitos.OS) {
gos = os
}
...@@ -28,6 +28,7 @@ func TestRewrite(t *testing.T) { ...@@ -28,6 +28,7 @@ func TestRewrite(t *testing.T) {
[]string{"/abc/", "ab", "/abc/{file}", ".html|"}, []string{"/abc/", "ab", "/abc/{file}", ".html|"},
[]string{"/abcd/", "ab", "/a/{dir}/{file}", ".html|"}, []string{"/abcd/", "ab", "/a/{dir}/{file}", ".html|"},
[]string{"/abcde/", "ab", "/a#{frag}", ".html|"}, []string{"/abcde/", "ab", "/a#{frag}", ".html|"},
[]string{"/ab/", `.*\.jpg`, "/ajpg", ""},
} }
for _, regexpRule := range regexpRules { for _, regexpRule := range regexpRules {
...@@ -76,6 +77,7 @@ func TestRewrite(t *testing.T) { ...@@ -76,6 +77,7 @@ func TestRewrite(t *testing.T) {
{"/abcd/abcd.html", "/a/abcd/abcd.html"}, {"/abcd/abcd.html", "/a/abcd/abcd.html"},
{"/abcde/abcde.html", "/a"}, {"/abcde/abcde.html", "/a"},
{"/abcde/abcde.html#1234", "/a#1234"}, {"/abcde/abcde.html#1234", "/a#1234"},
{"/ab/ab.jpg", "/ajpg"},
} }
for i, test := range tests { for i, test := range tests {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment