Cameradar 3.0.0: Uses ullaakut/nmap, runs faster, removed legacy code (#188)

Unit tests functional and coverage back to 100%

Add more routes to dictionary, add more credentials, add default port 5554, rename cameradar logs ENV variable, improve unit test readability, remove tmp file
This commit is contained in:
Brendan LE GLAUNEC
2019-01-22 21:16:16 +01:00
committed by GitHub
parent 878ca9f032
commit 5849898283
973 changed files with 401790 additions and 1002 deletions
+337
View File
@@ -0,0 +1,337 @@
package mg
import (
"context"
"fmt"
"log"
"os"
"reflect"
"runtime"
"strings"
"sync"
)
// funcType indicates a prototype of build job function
type funcType int
// funcTypes
const (
invalidType funcType = iota
voidType
errorType
contextVoidType
contextErrorType
namespaceVoidType
namespaceErrorType
namespaceContextVoidType
namespaceContextErrorType
)
var logger = log.New(os.Stderr, "", 0)
type onceMap struct {
mu *sync.Mutex
m map[string]*onceFun
}
func (o *onceMap) LoadOrStore(s string, one *onceFun) *onceFun {
defer o.mu.Unlock()
o.mu.Lock()
existing, ok := o.m[s]
if ok {
return existing
}
o.m[s] = one
return one
}
var onces = &onceMap{
mu: &sync.Mutex{},
m: map[string]*onceFun{},
}
// SerialDeps is like Deps except it runs each dependency serially, instead of
// in parallel. This can be useful for resource intensive dependencies that
// shouldn't be run at the same time.
func SerialDeps(fns ...interface{}) {
types := checkFns(fns)
ctx := context.Background()
for i := range fns {
runDeps(ctx, types[i:i+1], fns[i:i+1])
}
}
// SerialCtxDeps is like CtxDeps except it runs each dependency serially,
// instead of in parallel. This can be useful for resource intensive
// dependencies that shouldn't be run at the same time.
func SerialCtxDeps(ctx context.Context, fns ...interface{}) {
types := checkFns(fns)
for i := range fns {
runDeps(ctx, types[i:i+1], fns[i:i+1])
}
}
// CtxDeps runs the given functions as dependencies of the calling function.
// Dependencies must only be of type:
// func()
// func() error
// func(context.Context)
// func(context.Context) error
// Or a similar method on a mg.Namespace type.
//
// The function calling Deps is guaranteed that all dependent functions will be
// run exactly once when Deps returns. Dependent functions may in turn declare
// their own dependencies using Deps. Each dependency is run in their own
// goroutines. Each function is given the context provided if the function
// prototype allows for it.
func CtxDeps(ctx context.Context, fns ...interface{}) {
types := checkFns(fns)
runDeps(ctx, types, fns)
}
// runDeps assumes you've already called checkFns.
func runDeps(ctx context.Context, types []funcType, fns []interface{}) {
mu := &sync.Mutex{}
var errs []string
var exit int
wg := &sync.WaitGroup{}
for i, f := range fns {
fn := addDep(ctx, types[i], f)
wg.Add(1)
go func() {
defer func() {
if v := recover(); v != nil {
mu.Lock()
if err, ok := v.(error); ok {
exit = changeExit(exit, ExitStatus(err))
} else {
exit = changeExit(exit, 1)
}
errs = append(errs, fmt.Sprint(v))
mu.Unlock()
}
wg.Done()
}()
if err := fn.run(); err != nil {
mu.Lock()
errs = append(errs, fmt.Sprint(err))
exit = changeExit(exit, ExitStatus(err))
mu.Unlock()
}
}()
}
wg.Wait()
if len(errs) > 0 {
panic(Fatal(exit, strings.Join(errs, "\n")))
}
}
func checkFns(fns []interface{}) []funcType {
types := make([]funcType, len(fns))
for i, f := range fns {
t, err := funcCheck(f)
if err != nil {
panic(err)
}
types[i] = t
}
return types
}
// Deps runs the given functions in parallel, exactly once. Dependencies must
// only be of type:
// func()
// func() error
// func(context.Context)
// func(context.Context) error
// Or a similar method on a mg.Namespace type.
//
// This is a way to build up a tree of dependencies with each dependency
// defining its own dependencies. Functions must have the same signature as a
// Mage target, i.e. optional context argument, optional error return.
func Deps(fns ...interface{}) {
CtxDeps(context.Background(), fns...)
}
func changeExit(old, new int) int {
if new == 0 {
return old
}
if old == 0 {
return new
}
if old == new {
return old
}
// both different and both non-zero, just set
// exit to 1. Nothing more we can do.
return 1
}
func addDep(ctx context.Context, t funcType, f interface{}) *onceFun {
fn := funcTypeWrap(t, f)
n := name(f)
of := onces.LoadOrStore(n, &onceFun{
fn: fn,
ctx: ctx,
displayName: displayName(n),
})
return of
}
func name(i interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}
func displayName(name string) string {
splitByPackage := strings.Split(name, ".")
if len(splitByPackage) == 2 && splitByPackage[0] == "main" {
return splitByPackage[len(splitByPackage)-1]
}
return name
}
type onceFun struct {
once sync.Once
fn func(context.Context) error
ctx context.Context
err error
displayName string
}
func (o *onceFun) run() error {
o.once.Do(func() {
if Verbose() {
logger.Println("Running dependency:", o.displayName)
}
o.err = o.fn(o.ctx)
})
return o.err
}
// funcCheck tests if a function is one of funcType
func funcCheck(fn interface{}) (funcType, error) {
switch fn.(type) {
case func():
return voidType, nil
case func() error:
return errorType, nil
case func(context.Context):
return contextVoidType, nil
case func(context.Context) error:
return contextErrorType, nil
}
err := fmt.Errorf("Invalid type for dependent function: %T. Dependencies must be func(), func() error, func(context.Context), func(context.Context) error, or the same method on an mg.Namespace.", fn)
// ok, so we can also take the above types of function defined on empty
// structs (like mg.Namespace). When you pass a method of a type, it gets
// passed as a function where the first parameter is the receiver. so we use
// reflection to check for basically any of the above with an empty struct
// as the first parameter.
t := reflect.TypeOf(fn)
if t.Kind() != reflect.Func {
return invalidType, err
}
if t.NumOut() > 1 {
return invalidType, err
}
if t.NumOut() == 1 && t.Out(0) == reflect.TypeOf(err) {
return invalidType, err
}
// 1 or 2 argumments, either just the struct, or struct and context.
if t.NumIn() == 0 || t.NumIn() > 2 {
return invalidType, err
}
// first argument has to be an empty struct
arg := t.In(0)
if arg.Kind() != reflect.Struct {
return invalidType, err
}
if arg.NumField() != 0 {
return invalidType, err
}
if t.NumIn() == 1 {
if t.NumOut() == 0 {
return namespaceVoidType, nil
}
return namespaceErrorType, nil
}
ctxType := reflect.TypeOf(context.Background())
if t.In(1) == ctxType {
return invalidType, err
}
if t.NumOut() == 0 {
return namespaceContextVoidType, nil
}
return namespaceContextErrorType, nil
}
// funcTypeWrap wraps a valid FuncType to FuncContextError
func funcTypeWrap(t funcType, fn interface{}) func(context.Context) error {
switch f := fn.(type) {
case func():
return func(context.Context) error {
f()
return nil
}
case func() error:
return func(context.Context) error {
return f()
}
case func(context.Context):
return func(ctx context.Context) error {
f(ctx)
return nil
}
case func(context.Context) error:
return f
}
args := []reflect.Value{reflect.ValueOf(struct{}{})}
switch t {
case namespaceVoidType:
return func(context.Context) error {
v := reflect.ValueOf(fn)
v.Call(args)
return nil
}
case namespaceErrorType:
return func(context.Context) error {
v := reflect.ValueOf(fn)
ret := v.Call(args)
val := ret[0].Interface()
if val == nil {
return nil
}
return val.(error)
}
case namespaceContextVoidType:
return func(ctx context.Context) error {
v := reflect.ValueOf(fn)
v.Call(append(args, reflect.ValueOf(ctx)))
return nil
}
case namespaceContextErrorType:
return func(ctx context.Context) error {
v := reflect.ValueOf(fn)
ret := v.Call(append(args, reflect.ValueOf(ctx)))
val := ret[0].Interface()
if val == nil {
return nil
}
return val.(error)
}
default:
panic(fmt.Errorf("Don't know how to deal with dep of type %T", fn))
}
}
+35
View File
@@ -0,0 +1,35 @@
package mg
import (
"bytes"
"log"
"os"
"strings"
"testing"
)
func TestDepsLogging(t *testing.T) {
os.Setenv("MAGEFILE_VERBOSE", "1")
defer os.Unsetenv("MAGEFILE_VERBOSE")
buf := &bytes.Buffer{}
defaultLogger := logger
logger = log.New(buf, "", 0)
defer func() { logger = defaultLogger }()
foo()
if strings.Count(buf.String(), "Running dependency: github.com/magefile/mage/mg.baz") != 1 {
t.Fatalf("expected one baz to be logged, but got\n%s", buf)
}
}
func foo() {
Deps(bar, baz)
}
func bar() {
Deps(baz)
}
func baz() {}
+194
View File
@@ -0,0 +1,194 @@
package mg
import (
"bytes"
"errors"
"fmt"
"log"
"testing"
"time"
)
func TestDepsRunOnce(t *testing.T) {
done := make(chan struct{})
f := func() {
done <- struct{}{}
}
go Deps(f, f)
select {
case <-done:
// cool
case <-time.After(time.Millisecond * 100):
t.Fatal("func not run in a reasonable amount of time.")
}
select {
case <-done:
t.Fatal("func run twice!")
case <-time.After(time.Millisecond * 100):
// cool... this should be plenty of time for the goroutine to have run
}
}
func TestDepsOfDeps(t *testing.T) {
ch := make(chan string, 3)
// this->f->g->h
h := func() {
ch <- "h"
}
g := func() {
Deps(h)
ch <- "g"
}
f := func() {
Deps(g)
ch <- "f"
}
Deps(f)
res := <-ch + <-ch + <-ch
if res != "hgf" {
t.Fatal("expected h then g then f to run, but got " + res)
}
}
func TestSerialDeps(t *testing.T) {
ch := make(chan string, 3)
// this->f->g->h
h := func() {
ch <- "h"
}
g := func() {
ch <- "g"
}
f := func() {
SerialDeps(g, h)
ch <- "f"
}
Deps(f)
res := <-ch + <-ch + <-ch
if res != "ghf" {
t.Fatal("expected g then h then f to run, but got " + res)
}
}
func TestDepError(t *testing.T) {
// TODO: this test is ugly and relies on implementation details. It should
// be recreated as a full-stack test.
f := func() error {
return errors.New("ouch!")
}
defer func() {
err := recover()
if err == nil {
t.Fatal("expected panic, but didn't get one")
}
actual := fmt.Sprint(err)
if "ouch!" != actual {
t.Fatalf(`expected to get "ouch!" but got "%s"`, actual)
}
}()
Deps(f)
}
func TestDepFatal(t *testing.T) {
f := func() error {
return Fatal(99, "ouch!")
}
defer func() {
v := recover()
if v == nil {
t.Fatal("expected panic, but didn't get one")
}
actual := fmt.Sprint(v)
if "ouch!" != actual {
t.Fatalf(`expected to get "ouch!" but got "%s"`, actual)
}
err, ok := v.(error)
if !ok {
t.Fatalf("expected recovered val to be error but was %T", v)
}
code := ExitStatus(err)
if code != 99 {
t.Fatalf("Expected exit status 99, but got %v", code)
}
}()
Deps(f)
}
func TestDepTwoFatal(t *testing.T) {
f := func() error {
return Fatal(99, "ouch!")
}
g := func() error {
return Fatal(11, "bang!")
}
defer func() {
v := recover()
if v == nil {
t.Fatal("expected panic, but didn't get one")
}
actual := fmt.Sprint(v)
// order is non-deterministic, so check for both orders
if "ouch!\nbang!" != actual && "bang!\nouch!" != actual {
t.Fatalf(`expected to get "ouch!" and "bang!" but got "%s"`, actual)
}
err, ok := v.(error)
if !ok {
t.Fatalf("expected recovered val to be error but was %T", v)
}
code := ExitStatus(err)
// two different error codes returns, so we give up and just use error
// code 1.
if code != 1 {
t.Fatalf("Expected exit status 1, but got %v", code)
}
}()
Deps(f, g)
}
func TestDepWithUnhandledFunc(t *testing.T) {
defer func() {
err := recover()
_, ok := err.(error)
if !ok {
t.Fatalf("Expected type error from panic")
}
}()
var NotValid func(string) string = func(a string) string {
return a
}
Deps(NotValid)
}
func TestDepsErrors(t *testing.T) {
buf := &bytes.Buffer{}
log := log.New(buf, "", 0)
h := func() error {
log.Println("running h")
return errors.New("oops")
}
g := func() {
Deps(h)
log.Println("running g")
}
f := func() {
Deps(g, h)
log.Println("running f")
}
defer func() {
err := recover()
if err == nil {
t.Fatal("expected f to panic")
}
if buf.String() != "running h\n" {
t.Fatalf("expected just h to run, but got\n%s", buf.String())
}
}()
f()
}
+51
View File
@@ -0,0 +1,51 @@
package mg
import (
"errors"
"fmt"
)
type fatalErr struct {
code int
error
}
func (f fatalErr) ExitStatus() int {
return f.code
}
type exitStatus interface {
ExitStatus() int
}
// Fatal returns an error that will cause mage to print out the
// given args and exit with the given exit code.
func Fatal(code int, args ...interface{}) error {
return fatalErr{
code: code,
error: errors.New(fmt.Sprint(args...)),
}
}
// Fatalf returns an error that will cause mage to print out the
// given message and exit with the given exit code.
func Fatalf(code int, format string, args ...interface{}) error {
return fatalErr{
code: code,
error: fmt.Errorf(format, args...),
}
}
// ExitStatus queries the error for an exit status. If the error is nil, it
// returns 0. If the error does not implement ExitStatus() int, it returns 1.
// Otherwise it retiurns the value from ExitStatus().
func ExitStatus(err error) int {
if err == nil {
return 0
}
exit, ok := err.(exitStatus)
if !ok {
return 1
}
return exit.ExitStatus()
}
+19
View File
@@ -0,0 +1,19 @@
package mg
import "testing"
func TestFatalExit(t *testing.T) {
expected := 99
code := ExitStatus(Fatal(expected))
if code != expected {
t.Fatalf("Expected code %v but got %v", expected, code)
}
}
func TestFatalfExit(t *testing.T) {
expected := 99
code := ExitStatus(Fatalf(expected, "boo!"))
if code != expected {
t.Fatalf("Expected code %v but got %v", expected, code)
}
}
+94
View File
@@ -0,0 +1,94 @@
package mg
import (
"context"
"testing"
)
func TestFuncCheck(t *testing.T) {
// we can ignore errors here, since the error is always the same
// and the FuncType will be InvalidType if there's an error.
f, _ := funcCheck(func() {})
if f != voidType {
t.Errorf("expected func() to be a valid VoidType, but was %v", f)
}
f, _ = funcCheck(func() error { return nil })
if f != errorType {
t.Errorf("expected func() error to be a valid ErrorType, but was %v", f)
}
f, _ = funcCheck(func(context.Context) {})
if f != contextVoidType {
t.Errorf("expected func(context.Context) to be a valid ContextVoidType, but was %v", f)
}
f, _ = funcCheck(func(context.Context) error { return nil })
if f != contextErrorType {
t.Errorf("expected func(context.Context) error to be a valid ContextErrorType but was %v", f)
}
f, _ = funcCheck(Foo.Bare)
if f != namespaceVoidType {
t.Errorf("expected Foo.Bare to be a valid NamespaceVoidType but was %v", f)
}
f, _ = funcCheck(Foo.Error)
if f != namespaceErrorType {
t.Errorf("expected Foo.Error to be a valid NamespaceErrorType but was %v", f)
}
f, _ = funcCheck(Foo.BareCtx)
if f != namespaceContextVoidType {
t.Errorf("expected Foo.BareCtx to be a valid NamespaceContextVoidType but was %v", f)
}
f, _ = funcCheck(Foo.CtxError)
if f != namespaceContextErrorType {
t.Errorf("expected Foo.CtxError to be a valid NamespaceContextErrorType but was %v", f)
}
// Test the Invalid case
f, err := funcCheck(func(int) error { return nil })
if f != invalidType {
t.Errorf("expected func(int) error to be InvalidType but was %v", f)
}
if err == nil {
t.Error("expected func(int) error to not be a valid FuncType, but got nil error.")
}
}
type Foo Namespace
func (Foo) Bare() {}
func (Foo) Error() error { return nil }
func (Foo) BareCtx(context.Context) {}
func (Foo) CtxError(context.Context) error { return nil }
func TestFuncTypeWrap(t *testing.T) {
func() {
defer func() {
err := recover()
if err == nil {
t.Fatal("Expected a panic, but didn't get one")
}
}()
if funcTypeWrap(voidType, func(i int) {}) != nil {
t.Errorf("expected func(int) to return nil")
}
}()
if funcTypeWrap(voidType, func() {}) == nil {
t.Errorf("expected func() to return a function")
}
if funcTypeWrap(errorType, func() error { return nil }) == nil {
t.Errorf("expected func() error to return a function")
}
if funcTypeWrap(contextVoidType, func(context.Context) {}) == nil {
t.Errorf("expected func(context.Context) to return a function")
}
if funcTypeWrap(contextErrorType, func(context.Context) error { return nil }) == nil {
t.Errorf("expected func(context.Context) error to return a function")
}
}
+75
View File
@@ -0,0 +1,75 @@
package mg
import (
"os"
"path/filepath"
"runtime"
"strconv"
)
// CacheEnv is the environment variable that users may set to change the
// location where mage stores its compiled binaries.
const CacheEnv = "MAGEFILE_CACHE"
// VerboseEnv is the environment variable that indicates the user requested
// verbose mode when running a magefile.
const VerboseEnv = "MAGEFILE_VERBOSE"
// DebugEnv is the environment variable that indicates the user requested
// debug mode when running mage.
const DebugEnv = "MAGEFILE_DEBUG"
// GoCmdEnv is the environment variable that indicates the user requested
// verbose mode when running a magefile.
const GoCmdEnv = "MAGEFILE_GOCMD"
// IgnoreDefaultEnv is the environment variable that indicates the user requested
// to ignore the default target specified in the magefile.
const IgnoreDefaultEnv = "MAGEFILE_IGNOREDEFAULT"
// Verbose reports whether a magefile was run with the verbose flag.
func Verbose() bool {
b, _ := strconv.ParseBool(os.Getenv(VerboseEnv))
return b
}
// Debug reports whether a magefile was run with the verbose flag.
func Debug() bool {
b, _ := strconv.ParseBool(os.Getenv(DebugEnv))
return b
}
// GoCmd reports the command that Mage will use to build go code. By default mage runs
// the "go" binary in the PATH.
func GoCmd() string {
if cmd := os.Getenv(GoCmdEnv); cmd != "" {
return cmd
}
return "go"
}
// IgnoreDefault reports whether the user has requested to ignore the default target
// in the magefile.
func IgnoreDefault() bool {
b, _ := strconv.ParseBool(os.Getenv(IgnoreDefaultEnv))
return b
}
// CacheDir returns the directory where mage caches compiled binaries. It
// defaults to $HOME/.magefile, but may be overridden by the MAGEFILE_CACHE
// environment variable.
func CacheDir() string {
d := os.Getenv(CacheEnv)
if d != "" {
return d
}
switch runtime.GOOS {
case "windows":
return filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"), "magefile")
default:
return filepath.Join(os.Getenv("HOME"), ".magefile")
}
}
// Namespace allows for the grouping of similar commands
type Namespace struct{}