<commit_before>package program

import (
	"regexp"
	"strings"
)

// FunctionDefinition contains the prototype definition for a function.
type FunctionDefinition struct {
	// The name of the function, like "printf".
	Name string

	// The C return type, like "int".
	ReturnType string

	// The C argument types, like ["bool", "int"]. There is currently no way
	// to represent a varargs.
	ArgumentTypes []string

	// If this is not empty then this function name should be used instead
	// of the Name. Many low level functions have an exact match with a Go
	// function. For example, "sin()".
	Substitution string

	// Can be overriden with the substitution to rearrange the return variables
	// and parameters. When either of these are nil the behavior is to keep the
	// single return value and parameters the same.
	ReturnParameters []int
	Parameters       []int
}

var functionDefinitions map[string]FunctionDefinition

var builtInFunctionDefinitionsHaveBeenLoaded = false

// Each of the predefined function have a syntax that allows them to be easy to
// read (and maintain). For example:
//
//     double __builtin_fabs(double) -> darwin.Fabs
//
// Declares the prototype of __builtin_fabs (a low level function implemented
// only on Mac) with a specific substitution provided. This means that it should
// replace any instance of __builtin_fabs with:
//
//     github.com/elliotchance/c2go/darwin.Fabs
//
// The substitution is optional.
//
// The substituted function can also move the parameters and return value
// positions. This is called a transformation. For example:
//
//     size_t fread(void*, size_t, size_t, FILE*) -> $0, $1 = noarch.Fread($2, $3, $4)
//
// Where $0 represents the C return value and $1 and above are for each of the
// parameters.
//
// Transformations can also be used to specify varaible that need to be passed
// by reference by using the prefix "&" instead of "$":
//
//     size_t fread(void*, size_t, size_t, FILE*) -> $0 = noarch.Fread(&1, $2, $3, $4)
//
var builtInFunctionDefinitions = []string{
	// darwin/assert.h
	"int __builtin_expect(int, int) -> darwin.BuiltinExpect",
	"bool __assert_rtn(const char*, const char*, int, const char*) -> darwin.AssertRtn",

	// darwin/ctype.h
	"uint32 __istype(__darwin_ct_rune_t, uint32) -> darwin.IsType",
	"__darwin_ct_rune_t __isctype(__darwin_ct_rune_t, uint32) -> darwin.IsCType",
	"__darwin_ct_rune_t __tolower(__darwin_ct_rune_t) -> darwin.ToLower",
	"__darwin_ct_rune_t __toupper(__darwin_ct_rune_t) -> darwin.ToUpper",
	"uint32 __maskrune(__darwin_ct_rune_t, uint32) -> darwin.MaskRune",

	// linux/ctype.h
	"const unsigned short int** __ctype_b_loc() -> linux.CtypeLoc",
	"int tolower(int) -> linux.ToLower",
	"int toupper(int) -> linux.ToUpper",

	// linux/math.h
	"int __signbitf(float) -> noarch.Signbitf",
	"int __signbit(double) -> noarch.Signbitd",
	"int __signbitl(long double) -> noarch.Signbitl",
	"int __isnanf(float) -> linux.IsNanf",
	"int __isnan(double) -> math.IsNaN",
	"int __isnanl(long double) -> math.IsNaN",
	"int __isinff(float) -> linux.IsInff",
	"int __isinf(float) -> linux.IsInf",
	"int __isinfl(long double) -> linux.IsInf",

	// darwin/math.h
	"double __builtin_fabs(double) -> darwin.Fabs",
	"float __builtin_fabsf(float) -> darwin.Fabsf",
	"double __builtin_fabsl(double) -> darwin.Fabsl",
	"double __builtin_inf() -> darwin.Inf",
	"float __builtin_inff() -> darwin.Inff",
	"double __builtin_infl() -> darwin.Infl",
	"Double2 __sincospi_stret(double) -> darwin.SincospiStret",
	"Float2 __sincospif_stret(float) -> darwin.SincospifStret",
	"Double2 __sincos_stret(double) -> darwin.SincosStret",
	"Float2 __sincosf_stret(float) -> darwin.SincosfStret",
	"float __builtin_huge_valf() -> darwin.Inff",
	"int __inline_signbitf(float) -> noarch.Signbitf",
	"int __inline_signbitd(double) -> noarch.Signbitd",
	"int __inline_signbitl(long double) -> noarch.Signbitl",
	"double __builtin_nanf(const char*) -> darwin.NaN",

	// linux/assert.h
	"bool __assert_fail(const char*, const char*, unsigned int, const char*) -> linux.AssertFail",

	// linux/stdio.h
	"int _IO_getc(FILE*) -> noarch.Fgetc",
	"int _IO_putc(int, FILE*) -> noarch.Fputc",

	// math.h
	"double acos(double) -> math.Acos",
	"double asin(double) -> math.Asin",
	"double atan(double) -> math.Atan",
	"double atan2(double, double) -> math.Atan2",
	"double ceil(double) -> math.Ceil",
	"double cos(double) -> math.Cos",
	"double cosh(double) -> math.Cosh",
	"double exp(double) -> math.Exp",
	"double fabs(double) -> math.Abs",
	"double floor(double) -> math.Floor",
	"double fmod(double, double) -> math.Mod",
	"double ldexp(double, int) -> math.Ldexp",
	"double log(double) -> math.Log",
	"double log10(double) -> math.Log10",
	"double pow(double, double) -> math.Pow",
	"double sin(double) -> math.Sin",
	"double sinh(double) -> math.Sinh",
	"double sqrt(double) -> math.Sqrt",
	"double tan(double) -> math.Tan",
	"double tanh(double) -> math.Tanh",

	// stdio.h
	"int printf(const char*) -> noarch.Printf",
	"int scanf(const char*) -> noarch.Scanf",
	"int putchar(int) -> darwin.Putchar",
	"int puts(const char *) -> noarch.Puts",
	"FILE* fopen(const char *, const char *) -> noarch.Fopen",
	"int fclose(FILE*) -> noarch.Fclose",
	"int remove(const char*) -> noarch.Remove",
	"int rename(const char*, const char*) -> noarch.Rename",
	"int fputs(const char*, FILE*) -> noarch.Fputs",
	"FILE* tmpfile() -> noarch.Tmpfile",
	"char* fgets(char*, int, FILE*) -> noarch.Fgets",
	"void rewind(FILE*) -> noarch.Rewind",
	"int feof(FILE*) -> noarch.Feof",
	"char* tmpnam(char*) -> noarch.Tmpnam",
	"int fflush(FILE*) -> noarch.Fflush",
	"int fprintf(FILE*, const char*) -> noarch.Fprintf",
	"int fscanf(FILE*, const char*) -> noarch.Fscanf",
	"int fgetc(FILE*) -> noarch.Fgetc",
	"int fputc(int, FILE*) -> noarch.Fputc",
	"int getc(FILE*) -> noarch.Fgetc",
	"int getchar() -> noarch.Getchar",
	"int putc(int, FILE*) -> noarch.Fputc",
	"int fseek(FILE*, long int, int) -> noarch.Fseek",
	"long ftell(FILE*) -> noarch.Ftell",
	"int fread(void*, int, int, FILE*) -> $0 = noarch.Fread(&1, $2, $3, $4)",
	"int fwrite(char*, int, int, FILE*) -> noarch.Fwrite",
	"int fgetpos(FILE*, int*) -> noarch.Fgetpos",
	"int fsetpos(FILE*, int*) -> noarch.Fsetpos",

	// string.h
	"size_t strlen(const char*) -> noarch.Strlen",

	// stdlib.h
	"int atoi(const char*) -> noarch.Atoi",
	"long strtol(const char *, char **, int) -> noarch.Strtol",
	"void free(void*) -> noarch.Free",

	// I'm not sure which header file these comes from?
	"uint32 __builtin_bswap32(uint32) -> darwin.BSwap32",
	"uint64 __builtin_bswap64(uint64) -> darwin.BSwap64",
}

// GetFunctionDefinition will return nil if the function does not exist (is not
// registered).
func GetFunctionDefinition(functionName string) *FunctionDefinition {
	loadFunctionDefinitions()

	if f, ok := functionDefinitions[functionName]; ok {
		return &f
	}

	return nil
}

// AddFunctionDefinition registers a function definition. If the definition
// already exists it will be replaced.
func AddFunctionDefinition(f FunctionDefinition) {
	loadFunctionDefinitions()

	functionDefinitions[f.Name] = f
}

// dollarArgumentsToIntSlice converts a list of dollar arguments, like "$1, &2"
// into a slice of integers; [1, -2].
//
// This function requires at least one argument in s, but only arguments upto
// $9 or &9.
func dollarArgumentsToIntSlice(s string) []int {
	r := []int{}
	multiplier := 1

	for _, c := range s {
		if c == '$' {
			multiplier = 1
		}
		if c == '&' {
			multiplier = -1
		}

		if c >= '0' && c <= '9' {
			r = append(r, multiplier*(int(c)-'0'))
		}
	}

	return r
}

func loadFunctionDefinitions() {
	if builtInFunctionDefinitionsHaveBeenLoaded {
		return
	}

	functionDefinitions = map[string]FunctionDefinition{}
	builtInFunctionDefinitionsHaveBeenLoaded = true

	for _, f := range builtInFunctionDefinitions {
		match := regexp.MustCompile(`^(.+) ([^ ]+)\(([, a-z*A-Z_0-9]*)\)( -> .+)?$`).
			FindStringSubmatch(f)

		// Unpack argument types.
		argumentTypes := strings.Split(match[3], ",")
		for i := range argumentTypes {
			argumentTypes[i] = strings.TrimSpace(argumentTypes[i])
		}
		if len(argumentTypes) == 1 && argumentTypes[0] == "" {
			argumentTypes = []string{}
		}

		// Defaults for transformations.
		var returnParameters, parameters []int

		// Substitution rules.
		substitution := match[4]
		if substitution != "" {
			substitution = strings.TrimLeft(substitution, " ->")

			// The substitution might also rearrange the parameters (return and
			// parameter transformation).
			subMatch := regexp.MustCompile(`^(.*?) = (.*)\((.*)\)$`).
				FindStringSubmatch(substitution)
			if len(subMatch) > 0 {
				returnParameters = dollarArgumentsToIntSlice(subMatch[1])
				parameters = dollarArgumentsToIntSlice(subMatch[3])
				substitution = subMatch[2]
			}
		}

		if strings.HasPrefix(substitution, "darwin.") ||
			strings.HasPrefix(substitution, "linux.") ||
			strings.HasPrefix(substitution, "noarch.") {
			substitution = "github.com/elliotchance/c2go/" + substitution
		}

		AddFunctionDefinition(FunctionDefinition{
			Name:             match[2],
			ReturnType:       match[1],
			ArgumentTypes:    argumentTypes,
			Substitution:     substitution,
			ReturnParameters: returnParameters,
			Parameters:       parameters,
		})
	}
}
<commit_msg>Fix inf function on linux<commit_after>package program

import (
	"regexp"
	"strings"
)

// FunctionDefinition contains the prototype definition for a function.
type FunctionDefinition struct {
	// The name of the function, like "printf".
	Name string

	// The C return type, like "int".
	ReturnType string

	// The C argument types, like ["bool", "int"]. There is currently no way
	// to represent a varargs.
	ArgumentTypes []string

	// If this is not empty then this function name should be used instead
	// of the Name. Many low level functions have an exact match with a Go
	// function. For example, "sin()".
	Substitution string

	// Can be overriden with the substitution to rearrange the return variables
	// and parameters. When either of these are nil the behavior is to keep the
	// single return value and parameters the same.
	ReturnParameters []int
	Parameters       []int
}

var functionDefinitions map[string]FunctionDefinition

var builtInFunctionDefinitionsHaveBeenLoaded = false

// Each of the predefined function have a syntax that allows them to be easy to
// read (and maintain). For example:
//
//     double __builtin_fabs(double) -> darwin.Fabs
//
// Declares the prototype of __builtin_fabs (a low level function implemented
// only on Mac) with a specific substitution provided. This means that it should
// replace any instance of __builtin_fabs with:
//
//     github.com/elliotchance/c2go/darwin.Fabs
//
// The substitution is optional.
//
// The substituted function can also move the parameters and return value
// positions. This is called a transformation. For example:
//
//     size_t fread(void*, size_t, size_t, FILE*) -> $0, $1 = noarch.Fread($2, $3, $4)
//
// Where $0 represents the C return value and $1 and above are for each of the
// parameters.
//
// Transformations can also be used to specify varaible that need to be passed
// by reference by using the prefix "&" instead of "$":
//
//     size_t fread(void*, size_t, size_t, FILE*) -> $0 = noarch.Fread(&1, $2, $3, $4)
//
var builtInFunctionDefinitions = []string{
	// darwin/assert.h
	"int __builtin_expect(int, int) -> darwin.BuiltinExpect",
	"bool __assert_rtn(const char*, const char*, int, const char*) -> darwin.AssertRtn",

	// darwin/ctype.h
	"uint32 __istype(__darwin_ct_rune_t, uint32) -> darwin.IsType",
	"__darwin_ct_rune_t __isctype(__darwin_ct_rune_t, uint32) -> darwin.IsCType",
	"__darwin_ct_rune_t __tolower(__darwin_ct_rune_t) -> darwin.ToLower",
	"__darwin_ct_rune_t __toupper(__darwin_ct_rune_t) -> darwin.ToUpper",
	"uint32 __maskrune(__darwin_ct_rune_t, uint32) -> darwin.MaskRune",

	// linux/ctype.h
	"const unsigned short int** __ctype_b_loc() -> linux.CtypeLoc",
	"int tolower(int) -> linux.ToLower",
	"int toupper(int) -> linux.ToUpper",

	// linux/math.h
	"int __signbitf(float) -> noarch.Signbitf",
	"int __signbit(double) -> noarch.Signbitd",
	"int __signbitl(long double) -> noarch.Signbitl",
	"int __isnanf(float) -> linux.IsNanf",
	"int __isnan(double) -> math.IsNaN",
	"int __isnanl(long double) -> math.IsNaN",
	"int __isinff(float) -> linux.IsInff",
	"int __isinf(double) -> linux.IsInf",
	"int __isinfl(long double) -> linux.IsInf",

	// darwin/math.h
	"double __builtin_fabs(double) -> darwin.Fabs",
	"float __builtin_fabsf(float) -> darwin.Fabsf",
	"double __builtin_fabsl(double) -> darwin.Fabsl",
	"double __builtin_inf() -> darwin.Inf",
	"float __builtin_inff() -> darwin.Inff",
	"double __builtin_infl() -> darwin.Infl",
	"Double2 __sincospi_stret(double) -> darwin.SincospiStret",
	"Float2 __sincospif_stret(float) -> darwin.SincospifStret",
	"Double2 __sincos_stret(double) -> darwin.SincosStret",
	"Float2 __sincosf_stret(float) -> darwin.SincosfStret",
	"float __builtin_huge_valf() -> darwin.Inff",
	"int __inline_signbitf(float) -> noarch.Signbitf",
	"int __inline_signbitd(double) -> noarch.Signbitd",
	"int __inline_signbitl(long double) -> noarch.Signbitl",
	"double __builtin_nanf(const char*) -> darwin.NaN",

	// linux/assert.h
	"bool __assert_fail(const char*, const char*, unsigned int, const char*) -> linux.AssertFail",

	// linux/stdio.h
	"int _IO_getc(FILE*) -> noarch.Fgetc",
	"int _IO_putc(int, FILE*) -> noarch.Fputc",

	// math.h
	"double acos(double) -> math.Acos",
	"double asin(double) -> math.Asin",
	"double atan(double) -> math.Atan",
	"double atan2(double, double) -> math.Atan2",
	"double ceil(double) -> math.Ceil",
	"double cos(double) -> math.Cos",
	"double cosh(double) -> math.Cosh",
	"double exp(double) -> math.Exp",
	"double fabs(double) -> math.Abs",
	"double floor(double) -> math.Floor",
	"double fmod(double, double) -> math.Mod",
	"double ldexp(double, int) -> math.Ldexp",
	"double log(double) -> math.Log",
	"double log10(double) -> math.Log10",
	"double pow(double, double) -> math.Pow",
	"double sin(double) -> math.Sin",
	"double sinh(double) -> math.Sinh",
	"double sqrt(double) -> math.Sqrt",
	"double tan(double) -> math.Tan",
	"double tanh(double) -> math.Tanh",

	// stdio.h
	"int printf(const char*) -> noarch.Printf",
	"int scanf(const char*) -> noarch.Scanf",
	"int putchar(int) -> darwin.Putchar",
	"int puts(const char *) -> noarch.Puts",
	"FILE* fopen(const char *, const char *) -> noarch.Fopen",
	"int fclose(FILE*) -> noarch.Fclose",
	"int remove(const char*) -> noarch.Remove",
	"int rename(const char*, const char*) -> noarch.Rename",
	"int fputs(const char*, FILE*) -> noarch.Fputs",
	"FILE* tmpfile() -> noarch.Tmpfile",
	"char* fgets(char*, int, FILE*) -> noarch.Fgets",
	"void rewind(FILE*) -> noarch.Rewind",
	"int feof(FILE*) -> noarch.Feof",
	"char* tmpnam(char*) -> noarch.Tmpnam",
	"int fflush(FILE*) -> noarch.Fflush",
	"int fprintf(FILE*, const char*) -> noarch.Fprintf",
	"int fscanf(FILE*, const char*) -> noarch.Fscanf",
	"int fgetc(FILE*) -> noarch.Fgetc",
	"int fputc(int, FILE*) -> noarch.Fputc",
	"int getc(FILE*) -> noarch.Fgetc",
	"int getchar() -> noarch.Getchar",
	"int putc(int, FILE*) -> noarch.Fputc",
	"int fseek(FILE*, long int, int) -> noarch.Fseek",
	"long ftell(FILE*) -> noarch.Ftell",
	"int fread(void*, int, int, FILE*) -> $0 = noarch.Fread(&1, $2, $3, $4)",
	"int fwrite(char*, int, int, FILE*) -> noarch.Fwrite",
	"int fgetpos(FILE*, int*) -> noarch.Fgetpos",
	"int fsetpos(FILE*, int*) -> noarch.Fsetpos",

	// string.h
	"size_t strlen(const char*) -> noarch.Strlen",

	// stdlib.h
	"int atoi(const char*) -> noarch.Atoi",
	"long strtol(const char *, char **, int) -> noarch.Strtol",
	"void free(void*) -> noarch.Free",

	// I'm not sure which header file these comes from?
	"uint32 __builtin_bswap32(uint32) -> darwin.BSwap32",
	"uint64 __builtin_bswap64(uint64) -> darwin.BSwap64",
}

// GetFunctionDefinition will return nil if the function does not exist (is not
// registered).
func GetFunctionDefinition(functionName string) *FunctionDefinition {
	loadFunctionDefinitions()

	if f, ok := functionDefinitions[functionName]; ok {
		return &f
	}

	return nil
}

// AddFunctionDefinition registers a function definition. If the definition
// already exists it will be replaced.
func AddFunctionDefinition(f FunctionDefinition) {
	loadFunctionDefinitions()

	functionDefinitions[f.Name] = f
}

// dollarArgumentsToIntSlice converts a list of dollar arguments, like "$1, &2"
// into a slice of integers; [1, -2].
//
// This function requires at least one argument in s, but only arguments upto
// $9 or &9.
func dollarArgumentsToIntSlice(s string) []int {
	r := []int{}
	multiplier := 1

	for _, c := range s {
		if c == '$' {
			multiplier = 1
		}
		if c == '&' {
			multiplier = -1
		}

		if c >= '0' && c <= '9' {
			r = append(r, multiplier*(int(c)-'0'))
		}
	}

	return r
}

func loadFunctionDefinitions() {
	if builtInFunctionDefinitionsHaveBeenLoaded {
		return
	}

	functionDefinitions = map[string]FunctionDefinition{}
	builtInFunctionDefinitionsHaveBeenLoaded = true

	for _, f := range builtInFunctionDefinitions {
		match := regexp.MustCompile(`^(.+) ([^ ]+)\(([, a-z*A-Z_0-9]*)\)( -> .+)?$`).
			FindStringSubmatch(f)

		// Unpack argument types.
		argumentTypes := strings.Split(match[3], ",")
		for i := range argumentTypes {
			argumentTypes[i] = strings.TrimSpace(argumentTypes[i])
		}
		if len(argumentTypes) == 1 && argumentTypes[0] == "" {
			argumentTypes = []string{}
		}

		// Defaults for transformations.
		var returnParameters, parameters []int

		// Substitution rules.
		substitution := match[4]
		if substitution != "" {
			substitution = strings.TrimLeft(substitution, " ->")

			// The substitution might also rearrange the parameters (return and
			// parameter transformation).
			subMatch := regexp.MustCompile(`^(.*?) = (.*)\((.*)\)$`).
				FindStringSubmatch(substitution)
			if len(subMatch) > 0 {
				returnParameters = dollarArgumentsToIntSlice(subMatch[1])
				parameters = dollarArgumentsToIntSlice(subMatch[3])
				substitution = subMatch[2]
			}
		}

		if strings.HasPrefix(substitution, "darwin.") ||
			strings.HasPrefix(substitution, "linux.") ||
			strings.HasPrefix(substitution, "noarch.") {
			substitution = "github.com/elliotchance/c2go/" + substitution
		}

		AddFunctionDefinition(FunctionDefinition{
			Name:             match[2],
			ReturnType:       match[1],
			ArgumentTypes:    argumentTypes,
			Substitution:     substitution,
			ReturnParameters: returnParameters,
			Parameters:       parameters,
		})
	}
}
<|endoftext|>


<commit_before>package cgolmnl

/*
#cgo LDFLAGS: -lmnl
#include <libmnl/libmnl.h>
#include "cb.h"
#include "set_errno.h"
*/
import "C"

import (
	"unsafe"
	"syscall"
	// "fmt"
	// "os"
)

type MnlCb func(*Nlmsghdr, interface{}) (int, syscall.Errno)
type MnlCtlCb func(*Nlmsghdr, uint16, interface{}) (int, syscall.Errno)

// argp (param data in original function) has three elements
//   0: function pointer to MnlCtlCb
//   1: function pointer to MnlCb
//   2: (real) data
//
// packing at CbRun.?()
// unpacking below GoCb, GoCtlCb

//export GoCb
func GoCb(nlh *C.struct_nlmsghdr, argp unsafe.Pointer) C.int {
	args := *(*[3]unsafe.Pointer)(argp)
	cb := *(*MnlCb)(args[1])
	data := *(*interface{})(args[2])
	ret, err := cb((*Nlmsghdr)(unsafe.Pointer(nlh)), data) // returns (int, syscall.Errno)
	if err != 0 {
		// C.errno = int(err) // cannot refer to errno directly; see documentation
		C.SetErrno(C.int(err))
	}
	return C.int(ret)
}

//export GoCtlCb
func GoCtlCb(nlh *C.struct_nlmsghdr, msgtype C.uint16_t, argp unsafe.Pointer) C.int {
	args := *(*[3]unsafe.Pointer)(argp)
	cb := *(*MnlCtlCb)(args[0])
	data := *(*interface{})(args[2])
	ret, err := cb((*Nlmsghdr)(unsafe.Pointer(nlh)), uint16(msgtype), data)
	if err != 0 {
		C.SetErrno(C.int(err))
	}
	return C.int(ret)
}

/**
 * mnl_cb_run2 - callback runqueue for netlink messages
 *
 * int
 * mnl_cb_run2(const void *buf, size_t numbytes, unsigned int seq,
 *	       unsigned int portid, mnl_cb_t cb_data, void *data,
 *	       mnl_cb_t *cb_ctl_array, unsigned int cb_ctl_array_len)
 */
func CbRun3(buf []byte, seq, portid uint32,
	cb_data MnlCb, data interface{}, cb_ctl MnlCtlCb) (int, error) {
	args := [3]unsafe.Pointer{unsafe.Pointer(&cb_ctl), unsafe.Pointer(&cb_data), unsafe.Pointer(&data)}
	ret, err := C.cb_run3_wrapper(unsafe.Pointer(&buf[0]), C.size_t(len(buf)),
		C.uint32_t(seq), C.uint32_t(portid), unsafe.Pointer(&args))
	return int(ret), err
}

/**
 * mnl_cb_run - callback runqueue for netlink messages (simplified version)
 *
 * int
 * mnl_cb_run(const void *buf, size_t numbytes, uint32_t seq,
 *	      uint32_t portid, mnl_cb_t cb_data, void *data)
 */
func CbRun(buf []byte, seq, portid uint32, cb_data MnlCb, data interface{}) (int, error) {
	args := [3]unsafe.Pointer{unsafe.Pointer(nil), unsafe.Pointer(&cb_data), unsafe.Pointer(&data)}
	ret, err := C.cb_run_wrapper(unsafe.Pointer(&buf[0]), C.size_t(len(buf)),
		C.uint32_t(seq), C.uint32_t(portid), unsafe.Pointer(&args))
	return int(ret), err
}
<commit_msg>callback: nil checking<commit_after>package cgolmnl

/*
#cgo LDFLAGS: -lmnl
#include <libmnl/libmnl.h>
#include "cb.h"
#include "set_errno.h"
*/
import "C"

import (
	"unsafe"
	"syscall"
	// "fmt"
	// "os"
)

type MnlCb func(*Nlmsghdr, interface{}) (int, syscall.Errno)
type MnlCtlCb func(*Nlmsghdr, uint16, interface{}) (int, syscall.Errno)

// argp (param data in original function) has three elements
//   0: function pointer to MnlCtlCb
//   1: function pointer to MnlCb
//   2: (real) data
//
// packing at CbRun.?()
// unpacking below GoCb, GoCtlCb

//export GoCb
func GoCb(nlh *C.struct_nlmsghdr, argp unsafe.Pointer) C.int {
	args := *(*[3]unsafe.Pointer)(argp)
	cb := *(*MnlCb)(args[1])
	if cb == nil {
		return MNL_CB_OK
	}
	data := *(*interface{})(args[2])
	ret, err := cb((*Nlmsghdr)(unsafe.Pointer(nlh)), data) // returns (int, syscall.Errno)
	if err != 0 {
		// C.errno = int(err) // cannot refer to errno directly; see documentation
		C.SetErrno(C.int(err))
	}
	return C.int(ret)
}

//export GoCtlCb
func GoCtlCb(nlh *C.struct_nlmsghdr, msgtype C.uint16_t, argp unsafe.Pointer) C.int {
	args := *(*[3]unsafe.Pointer)(argp)
	cb := *(*MnlCtlCb)(args[0])
	data := *(*interface{})(args[2])
	ret, err := cb((*Nlmsghdr)(unsafe.Pointer(nlh)), uint16(msgtype), data)
	if err != 0 {
		C.SetErrno(C.int(err))
	}
	return C.int(ret)
}

/**
 * mnl_cb_run2 - callback runqueue for netlink messages
 *
 * int
 * mnl_cb_run2(const void *buf, size_t numbytes, unsigned int seq,
 *	       unsigned int portid, mnl_cb_t cb_data, void *data,
 *	       mnl_cb_t *cb_ctl_array, unsigned int cb_ctl_array_len)
 */
func CbRun3(buf []byte, seq, portid uint32,
	cb_data MnlCb, data interface{}, cb_ctl MnlCtlCb) (int, error) {
	args := [3]unsafe.Pointer{unsafe.Pointer(&cb_ctl), unsafe.Pointer(&cb_data), unsafe.Pointer(&data)}
	ret, err := C.cb_run3_wrapper(unsafe.Pointer(&buf[0]), C.size_t(len(buf)),
		C.uint32_t(seq), C.uint32_t(portid), unsafe.Pointer(&args))
	return int(ret), err
}

/**
 * mnl_cb_run - callback runqueue for netlink messages (simplified version)
 *
 * int
 * mnl_cb_run(const void *buf, size_t numbytes, uint32_t seq,
 *	      uint32_t portid, mnl_cb_t cb_data, void *data)
 */
func CbRun(buf []byte, seq, portid uint32, cb_data MnlCb, data interface{}) (int, error) {
	args := [3]unsafe.Pointer{unsafe.Pointer(nil), unsafe.Pointer(&cb_data), unsafe.Pointer(&data)}
	ret, err := C.cb_run_wrapper(unsafe.Pointer(&buf[0]), C.size_t(len(buf)),
		C.uint32_t(seq), C.uint32_t(portid), unsafe.Pointer(&args))
	return int(ret), err
}
<|endoftext|>


<commit_before>package config

import (
	"compress/gzip"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/getlantern/appdir"
	"github.com/getlantern/fronted"
	"github.com/getlantern/golog"
	"github.com/getlantern/yaml"
	"github.com/getlantern/yamlconf"

	"github.com/getlantern/flashlight/client"
	"github.com/getlantern/flashlight/globals"
	"github.com/getlantern/flashlight/server"
	"github.com/getlantern/flashlight/statreporter"
	"github.com/getlantern/flashlight/util"
)

const (
	CloudConfigPollInterval = 1 * time.Minute

	cloudflare  = "cloudflare"
	etag        = "ETag"
	ifNoneMatch = "If-None-Match"
)

var (
	log                 = golog.LoggerFor("flashlight.config")
	m                   *yamlconf.Manager
	lastCloudConfigETag = ""
)

type Config struct {
	Version       int
	CloudConfig   string
	CloudConfigCA string
	Addr          string
	Role          string
	InstanceId    string
	Country       string
	StatsAddr     string
	CpuProfile    string
	MemProfile    string
	Stats         *statreporter.Config
	Server        *server.ServerConfig
	Client        *client.ClientConfig
	TrustedCAs    []*CA
}

// CA represents a certificate authority
type CA struct {
	CommonName string
	Cert       string // PEM-encoded
}

// Start starts the configuration system.
func Start(updateHandler func(updated *Config)) (*Config, error) {
	m = &yamlconf.Manager{
		FilePath:         InConfigDir("lantern.yaml"),
		FilePollInterval: 1 * time.Second,
		ConfigServerAddr: *configaddr,
		EmptyConfig: func() yamlconf.Config {
			return &Config{}
		},
		OneTimeSetup: func(ycfg yamlconf.Config) error {
			cfg := ycfg.(*Config)
			return cfg.applyFlags()
		},
		CustomPoll: func(currentCfg yamlconf.Config) (mutate func(yamlconf.Config) error, waitTime time.Duration, err error) {
			cfg := currentCfg.(*Config)
			waitTime = cfg.cloudPollSleepTime()
			if cfg.CloudConfig == "" {
				// Config doesn't have a CloudConfig, just ignore
				mutate = func(ycfg yamlconf.Config) error {
					// do nothing
					return nil
				}
				return
			}

			var bytes []byte
			bytes, err = cfg.fetchCloudConfig()
			if err == nil {
				mutate = func(ycfg yamlconf.Config) error {
					log.Debugf("Merging cloud configuration")
					cfg := ycfg.(*Config)
					return cfg.updateFrom(bytes)
				}
			}
			return
		},
	}
	initial, err := m.Start()
	var cfg *Config
	if err == nil {
		cfg = initial.(*Config)
		updateGlobals(cfg)
		go func() {
			// Read updates
			for {
				next := m.Next()
				nextCfg := next.(*Config)
				updateGlobals(nextCfg)
				updateHandler(nextCfg)
			}
		}()
	}
	return cfg, err
}

func updateGlobals(cfg *Config) {
	globals.InstanceId = cfg.InstanceId
	globals.Country = cfg.Country
	err := globals.SetTrustedCAs(cfg.TrustedCACerts())
	if err != nil {
		log.Fatalf("Unable to configure trusted CAs: %s", err)
	}
}

// Update updates the configuration using the given mutator function.
func Update(mutate func(cfg *Config) error) error {
	return m.Update(func(ycfg yamlconf.Config) error {
		return mutate(ycfg.(*Config))
	})
}

// InConfigDir returns the path to the given filename inside of the configdir.
func InConfigDir(filename string) string {
	cdir := *configdir
	if cdir == "" {
		cdir = appdir.General("Lantern")
	}
	log.Debugf("Placing configuration in %v", cdir)
	if _, err := os.Stat(cdir); err != nil {
		if os.IsNotExist(err) {
			// Create config dir
			if err := os.MkdirAll(cdir, 0755); err != nil {
				log.Fatalf("Unable to create configdir at %s: %s", cdir, err)
			}
		}
	}
	return filepath.Join(cdir, filename)
}

// TrustedCACerts returns a slice of PEM-encoded certs for the trusted CAs
func (cfg *Config) TrustedCACerts() []string {
	certs := make([]string, 0, len(cfg.TrustedCAs))
	for _, ca := range cfg.TrustedCAs {
		certs = append(certs, ca.Cert)
	}
	return certs
}

// GetVersion implements the method from interface yamlconf.Config
func (cfg *Config) GetVersion() int {
	return cfg.Version
}

// SetVersion implements the method from interface yamlconf.Config
func (cfg *Config) SetVersion(version int) {
	cfg.Version = version
}

// ApplyDefaults implements the method from interface yamlconf.Config
//
// ApplyDefaults populates default values on a Config to make sure that we have
// a minimum viable config for running.  As new settings are added to
// flashlight, this function should be updated to provide sensible defaults for
// those settings.
func (cfg *Config) ApplyDefaults() {
	if cfg.Role == "" {
		cfg.Role = "client"
	}

	if cfg.Addr == "" {
		cfg.Addr = "localhost:8787"
	}

	// Default country
	if cfg.Country == "" {
		cfg.Country = *country
	}

	// Make sure we always have a stats config
	if cfg.Stats == nil {
		cfg.Stats = &statreporter.Config{}
	}

	if cfg.Stats.StatshubAddr == "" {
		cfg.Stats.StatshubAddr = *statshubAddr
	}

	if cfg.Client != nil && cfg.Role == "client" {
		cfg.applyClientDefaults()
	}

	if cfg.TrustedCAs == nil || len(cfg.TrustedCAs) == 0 {
		cfg.TrustedCAs = defaultTrustedCAs
	}
}

func (cfg *Config) applyClientDefaults() {
	// Make sure we always have at least one masquerade set
	if cfg.Client.MasqueradeSets == nil {
		cfg.Client.MasqueradeSets = make(map[string][]*fronted.Masquerade)
	}
	if len(cfg.Client.MasqueradeSets) == 0 {
		cfg.Client.MasqueradeSets[cloudflare] = cloudflareMasquerades
	}

	// Make sure we always have at least one server
	if cfg.Client.FrontedServers == nil {
		cfg.Client.FrontedServers = make([]*client.FrontedServerInfo, 0)
	}
	if len(cfg.Client.FrontedServers) == 0 && len(cfg.Client.ChainedServers) == 0 {
		cfg.Client.FrontedServers = append(cfg.Client.FrontedServers, &client.FrontedServerInfo{
			Host:           "fallbacks.getiantem.org",
			Port:           443,
			PoolSize:       30,
			MasqueradeSet:  cloudflare,
			MaxMasquerades: 20,
			QOS:            10,
			Weight:         4000,
		}, &client.FrontedServerInfo{
			Host:           "peers.getiantem.org",
			Port:           443,
			PoolSize:       30,
			MasqueradeSet:  cloudflare,
			MaxMasquerades: 20,
			QOS:            2,
			Weight:         1000,
		})
	}

	// Make sure all servers have a QOS and Weight configured
	for _, server := range cfg.Client.FrontedServers {
		if server.QOS == 0 {
			server.QOS = 5
		}
		if server.Weight == 0 {
			server.Weight = 100
		}
		if server.RedialAttempts == 0 {
			server.RedialAttempts = 2
		}
	}

	// Always make sure we have a map of ChainedServers
	if cfg.Client.ChainedServers == nil {
		cfg.Client.ChainedServers = make(map[string]*client.ChainedServerInfo)
	}

	// Sort servers so that they're always in a predictable order
	cfg.Client.SortServers()
}

func (cfg *Config) IsDownstream() bool {
	return cfg.Role == "client"
}

func (cfg *Config) IsUpstream() bool {
	return !cfg.IsDownstream()
}

func (cfg Config) cloudPollSleepTime() time.Duration {
	return time.Duration((CloudConfigPollInterval.Nanoseconds() / 2) + rand.Int63n(CloudConfigPollInterval.Nanoseconds()))
}

func (cfg Config) fetchCloudConfig() ([]byte, error) {
	log.Debugf("Fetching cloud config from: %s", cfg.CloudConfig)
	// Try it unproxied first
	bytes, err := cfg.doFetchCloudConfig("")
	if err != nil && cfg.IsDownstream() {
		// If that failed, try it proxied
		bytes, err = cfg.doFetchCloudConfig(cfg.Addr)
	}
	if err != nil {
		return nil, fmt.Errorf("Unable to read yaml from %s: %s", cfg.CloudConfig, err)
	}
	return bytes, err
}

func (cfg Config) doFetchCloudConfig(proxyAddr string) ([]byte, error) {
	client, err := util.HTTPClient(cfg.CloudConfigCA, proxyAddr)
	if err != nil {
		return nil, fmt.Errorf("Unable to initialize HTTP client: %s", err)
	}
	log.Debugf("Checking for cloud configuration at: %s", cfg.CloudConfig)
	req, err := http.NewRequest("GET", cfg.CloudConfig, nil)
	if err != nil {
		return nil, fmt.Errorf("Unable to construct request for cloud config at %s: %s", cfg.CloudConfig, err)
	}
	if lastCloudConfigETag != "" {
		// Don't bother fetching if unchanged
		req.Header.Set(ifNoneMatch, lastCloudConfigETag)
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("Unable to fetch cloud config at %s: %s", cfg.CloudConfig, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode == 304 {
		log.Debugf("Config unchanged in cloud")
		return nil, nil
	} else if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Unexpected response status: %d", resp.StatusCode)
	}
	lastCloudConfigETag = resp.Header.Get(etag)
	gzReader, err := gzip.NewReader(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("Unable to open gzip reader: %s", err)
	}
	return ioutil.ReadAll(gzReader)
}

// updateFrom creates a new Config by merging the given yaml into this Config.
// Any servers in the updated yaml replace ones in the original Config and any
// masquerade sets in the updated yaml replace ones in the original Config.
func (updated *Config) updateFrom(updateBytes []byte) error {
	err := yaml.Unmarshal(updateBytes, updated)
	if err != nil {
		return fmt.Errorf("Unable to unmarshal YAML for update: %s", err)
	}
	// Need to de-duplicate servers, since yaml appends them
	servers := make(map[string]*client.FrontedServerInfo)
	for _, server := range updated.Client.FrontedServers {
		servers[server.Host] = server
	}
	updated.Client.FrontedServers = make([]*client.FrontedServerInfo, 0, len(servers))
	for _, server := range servers {
		updated.Client.FrontedServers = append(updated.Client.FrontedServers, server)
	}
	return nil
}
<commit_msg>always proxy fetching of config updates when running as client<commit_after>package config

import (
	"compress/gzip"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/getlantern/appdir"
	"github.com/getlantern/fronted"
	"github.com/getlantern/golog"
	"github.com/getlantern/yaml"
	"github.com/getlantern/yamlconf"

	"github.com/getlantern/flashlight/client"
	"github.com/getlantern/flashlight/globals"
	"github.com/getlantern/flashlight/server"
	"github.com/getlantern/flashlight/statreporter"
	"github.com/getlantern/flashlight/util"
)

const (
	CloudConfigPollInterval = 1 * time.Minute

	cloudflare  = "cloudflare"
	etag        = "ETag"
	ifNoneMatch = "If-None-Match"
)

var (
	log                 = golog.LoggerFor("flashlight.config")
	m                   *yamlconf.Manager
	lastCloudConfigETag = ""
)

type Config struct {
	Version       int
	CloudConfig   string
	CloudConfigCA string
	Addr          string
	Role          string
	InstanceId    string
	Country       string
	StatsAddr     string
	CpuProfile    string
	MemProfile    string
	Stats         *statreporter.Config
	Server        *server.ServerConfig
	Client        *client.ClientConfig
	TrustedCAs    []*CA
}

// CA represents a certificate authority
type CA struct {
	CommonName string
	Cert       string // PEM-encoded
}

// Start starts the configuration system.
func Start(updateHandler func(updated *Config)) (*Config, error) {
	m = &yamlconf.Manager{
		FilePath:         InConfigDir("lantern.yaml"),
		FilePollInterval: 1 * time.Second,
		ConfigServerAddr: *configaddr,
		EmptyConfig: func() yamlconf.Config {
			return &Config{}
		},
		OneTimeSetup: func(ycfg yamlconf.Config) error {
			cfg := ycfg.(*Config)
			return cfg.applyFlags()
		},
		CustomPoll: func(currentCfg yamlconf.Config) (mutate func(yamlconf.Config) error, waitTime time.Duration, err error) {
			cfg := currentCfg.(*Config)
			waitTime = cfg.cloudPollSleepTime()
			if cfg.CloudConfig == "" {
				// Config doesn't have a CloudConfig, just ignore
				mutate = func(ycfg yamlconf.Config) error {
					// do nothing
					return nil
				}
				return
			}

			var bytes []byte
			bytes, err = cfg.fetchCloudConfig()
			if err == nil {
				mutate = func(ycfg yamlconf.Config) error {
					log.Debugf("Merging cloud configuration")
					cfg := ycfg.(*Config)
					return cfg.updateFrom(bytes)
				}
			}
			return
		},
	}
	initial, err := m.Start()
	var cfg *Config
	if err == nil {
		cfg = initial.(*Config)
		updateGlobals(cfg)
		go func() {
			// Read updates
			for {
				next := m.Next()
				nextCfg := next.(*Config)
				updateGlobals(nextCfg)
				updateHandler(nextCfg)
			}
		}()
	}
	return cfg, err
}

func updateGlobals(cfg *Config) {
	globals.InstanceId = cfg.InstanceId
	globals.Country = cfg.Country
	err := globals.SetTrustedCAs(cfg.TrustedCACerts())
	if err != nil {
		log.Fatalf("Unable to configure trusted CAs: %s", err)
	}
}

// Update updates the configuration using the given mutator function.
func Update(mutate func(cfg *Config) error) error {
	return m.Update(func(ycfg yamlconf.Config) error {
		return mutate(ycfg.(*Config))
	})
}

// InConfigDir returns the path to the given filename inside of the configdir.
func InConfigDir(filename string) string {
	cdir := *configdir
	if cdir == "" {
		cdir = appdir.General("Lantern")
	}
	log.Debugf("Placing configuration in %v", cdir)
	if _, err := os.Stat(cdir); err != nil {
		if os.IsNotExist(err) {
			// Create config dir
			if err := os.MkdirAll(cdir, 0755); err != nil {
				log.Fatalf("Unable to create configdir at %s: %s", cdir, err)
			}
		}
	}
	return filepath.Join(cdir, filename)
}

// TrustedCACerts returns a slice of PEM-encoded certs for the trusted CAs
func (cfg *Config) TrustedCACerts() []string {
	certs := make([]string, 0, len(cfg.TrustedCAs))
	for _, ca := range cfg.TrustedCAs {
		certs = append(certs, ca.Cert)
	}
	return certs
}

// GetVersion implements the method from interface yamlconf.Config
func (cfg *Config) GetVersion() int {
	return cfg.Version
}

// SetVersion implements the method from interface yamlconf.Config
func (cfg *Config) SetVersion(version int) {
	cfg.Version = version
}

// ApplyDefaults implements the method from interface yamlconf.Config
//
// ApplyDefaults populates default values on a Config to make sure that we have
// a minimum viable config for running.  As new settings are added to
// flashlight, this function should be updated to provide sensible defaults for
// those settings.
func (cfg *Config) ApplyDefaults() {
	if cfg.Role == "" {
		cfg.Role = "client"
	}

	if cfg.Addr == "" {
		cfg.Addr = "localhost:8787"
	}

	// Default country
	if cfg.Country == "" {
		cfg.Country = *country
	}

	// Make sure we always have a stats config
	if cfg.Stats == nil {
		cfg.Stats = &statreporter.Config{}
	}

	if cfg.Stats.StatshubAddr == "" {
		cfg.Stats.StatshubAddr = *statshubAddr
	}

	if cfg.Client != nil && cfg.Role == "client" {
		cfg.applyClientDefaults()
	}

	if cfg.TrustedCAs == nil || len(cfg.TrustedCAs) == 0 {
		cfg.TrustedCAs = defaultTrustedCAs
	}
}

func (cfg *Config) applyClientDefaults() {
	// Make sure we always have at least one masquerade set
	if cfg.Client.MasqueradeSets == nil {
		cfg.Client.MasqueradeSets = make(map[string][]*fronted.Masquerade)
	}
	if len(cfg.Client.MasqueradeSets) == 0 {
		cfg.Client.MasqueradeSets[cloudflare] = cloudflareMasquerades
	}

	// Make sure we always have at least one server
	if cfg.Client.FrontedServers == nil {
		cfg.Client.FrontedServers = make([]*client.FrontedServerInfo, 0)
	}
	if len(cfg.Client.FrontedServers) == 0 && len(cfg.Client.ChainedServers) == 0 {
		cfg.Client.FrontedServers = append(cfg.Client.FrontedServers, &client.FrontedServerInfo{
			Host:           "fallbacks.getiantem.org",
			Port:           443,
			PoolSize:       30,
			MasqueradeSet:  cloudflare,
			MaxMasquerades: 20,
			QOS:            10,
			Weight:         4000,
		}, &client.FrontedServerInfo{
			Host:           "peers.getiantem.org",
			Port:           443,
			PoolSize:       30,
			MasqueradeSet:  cloudflare,
			MaxMasquerades: 20,
			QOS:            2,
			Weight:         1000,
		})
	}

	// Make sure all servers have a QOS and Weight configured
	for _, server := range cfg.Client.FrontedServers {
		if server.QOS == 0 {
			server.QOS = 5
		}
		if server.Weight == 0 {
			server.Weight = 100
		}
		if server.RedialAttempts == 0 {
			server.RedialAttempts = 2
		}
	}

	// Always make sure we have a map of ChainedServers
	if cfg.Client.ChainedServers == nil {
		cfg.Client.ChainedServers = make(map[string]*client.ChainedServerInfo)
	}

	// Sort servers so that they're always in a predictable order
	cfg.Client.SortServers()
}

func (cfg *Config) IsDownstream() bool {
	return cfg.Role == "client"
}

func (cfg *Config) IsUpstream() bool {
	return !cfg.IsDownstream()
}

func (cfg Config) cloudPollSleepTime() time.Duration {
	return time.Duration((CloudConfigPollInterval.Nanoseconds() / 2) + rand.Int63n(CloudConfigPollInterval.Nanoseconds()))
}

func (cfg Config) fetchCloudConfig() (bytes []byte, err error) {
	log.Debugf("Fetching cloud config from: %s", cfg.CloudConfig)

	if cfg.IsDownstream() {
		// Clients must always proxy the request
		if cfg.Addr == "" {
			err = fmt.Errorf("No proxyAddr")
		} else {
			bytes, err = cfg.doFetchCloudConfig(cfg.Addr)
		}
	} else {
		bytes, err = cfg.doFetchCloudConfig("")
	}
	if err != nil {
		bytes = nil
		err = fmt.Errorf("Unable to read yaml from %s: %s", cfg.CloudConfig, err)
	}
	return
}

func (cfg Config) doFetchCloudConfig(proxyAddr string) ([]byte, error) {
	log.Tracef("doFetchCloudConfig via '%s'", proxyAddr)
	client, err := util.HTTPClient(cfg.CloudConfigCA, proxyAddr)
	if err != nil {
		return nil, fmt.Errorf("Unable to initialize HTTP client: %s", err)
	}
	log.Debugf("Checking for cloud configuration at: %s", cfg.CloudConfig)
	req, err := http.NewRequest("GET", cfg.CloudConfig, nil)
	if err != nil {
		return nil, fmt.Errorf("Unable to construct request for cloud config at %s: %s", cfg.CloudConfig, err)
	}
	if lastCloudConfigETag != "" {
		// Don't bother fetching if unchanged
		req.Header.Set(ifNoneMatch, lastCloudConfigETag)
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("Unable to fetch cloud config at %s: %s", cfg.CloudConfig, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode == 304 {
		log.Debugf("Config unchanged in cloud")
		return nil, nil
	} else if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Unexpected response status: %d", resp.StatusCode)
	}
	lastCloudConfigETag = resp.Header.Get(etag)
	gzReader, err := gzip.NewReader(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("Unable to open gzip reader: %s", err)
	}
	return ioutil.ReadAll(gzReader)
}

// updateFrom creates a new Config by merging the given yaml into this Config.
// Any servers in the updated yaml replace ones in the original Config and any
// masquerade sets in the updated yaml replace ones in the original Config.
func (updated *Config) updateFrom(updateBytes []byte) error {
	err := yaml.Unmarshal(updateBytes, updated)
	if err != nil {
		return fmt.Errorf("Unable to unmarshal YAML for update: %s", err)
	}
	// Need to de-duplicate servers, since yaml appends them
	servers := make(map[string]*client.FrontedServerInfo)
	for _, server := range updated.Client.FrontedServers {
		servers[server.Host] = server
	}
	updated.Client.FrontedServers = make([]*client.FrontedServerInfo, 0, len(servers))
	for _, server := range servers {
		updated.Client.FrontedServers = append(updated.Client.FrontedServers, server)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Koichi Shiraishi. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package context

import (
	"go/build"
	"os"
	"path/filepath"
	"runtime"
	"sync"
)

// A Context specifies the supporting context for a build and embedded
// build.Context type struct.
type Build struct {
	Tool string
	build.Context
}

// GoPath return the new GOPATH estimated from the path p directory structure.
func (ctxt *Build) buildContext(p string) (string, string) {
	tool := "go"

	// Get original $GOPATH path.
	goPath := os.Getenv("GOPATH")

	// Get runtime $GOROOT path and join to goPath.
	r := runtime.GOROOT()
	if r != "" {
		goPath = goPath + string(filepath.ListSeparator) + r
	}

	// Cleanup directory path.
	p = filepath.Clean(p)

	// Check the path p are Gb directory structure.
	// If yes, append gb root and vendor path to the goPath lists.
	if gbpath, yes := ctxt.isGb(p); yes {
		goPath = gbpath + string(filepath.ListSeparator) +
			filepath.Join(gbpath, "vendor") + string(filepath.ListSeparator) +
			goPath
		tool = "gb"
	}

	return goPath, tool
}

// isGb return the gb package root path if p is gb project directory structure.
func (ctxt *Build) isGb(p string) (string, bool) {
	ctxt.Context = build.Default

	var pkgRoot string
	for {
		pkg, err := ctxt.ImportDir(p, build.IgnoreVendor)
		if err != nil {
			return "", err != nil
			break
		}

		if pkg.Name == "main" {
			pkgRoot = pkg.Dir
			break
		}
		p = filepath.Dir(p)
		continue
	}

	// gb project directory is `../../pkgRoot`
	projRoot, src := filepath.Split(filepath.Dir(pkgRoot))

	_, err := os.Stat(filepath.Join(filepath.Clean(projRoot), "vendor/manifest"))

	return filepath.Clean(projRoot), (err != nil || src == "src")
}

// contextMu Mutex lock for SetContext.
var contextMu sync.Mutex

// SetContext sets the go/build Default.GOPATH and $GOPATH to GoPath(p)
// under a mutex.
// The returned function restores Default.GOPATH to its original value and
// unlocks the mutex.
//
// This function intended to be used to the go/build Default.
func (c *Build) SetContext(p string) func() {
	contextMu.Lock()
	original := build.Default.GOPATH

	build.Default.GOPATH, c.Tool = c.buildContext(p)
	os.Setenv("GOPATH", build.Default.GOPATH)

	return func() {
		build.Default.GOPATH = original
		os.Setenv("GOPATH", build.Default.GOPATH)
		contextMu.Unlock()
	}
}
<commit_msg>Fix isGo to correctly err is nil<commit_after>// Copyright 2016 Koichi Shiraishi. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package context

import (
	"go/build"
	"os"
	"path/filepath"
	"runtime"
	"sync"
)

// A Context specifies the supporting context for a build and embedded
// build.Context type struct.
type Build struct {
	Tool string
	build.Context
}

// GoPath return the new GOPATH estimated from the path p directory structure.
func (ctxt *Build) buildContext(p string) (string, string) {
	tool := "go"

	// Get original $GOPATH path.
	goPath := os.Getenv("GOPATH")

	// Get runtime $GOROOT path and join to goPath.
	r := runtime.GOROOT()
	if r != "" {
		goPath = goPath + string(filepath.ListSeparator) + r
	}

	// Cleanup directory path.
	p = filepath.Clean(p)

	// Check the path p are Gb directory structure.
	// If yes, append gb root and vendor path to the goPath lists.
	if gbpath, yes := ctxt.isGb(p); yes {
		goPath = gbpath + string(filepath.ListSeparator) +
			filepath.Join(gbpath, "vendor") + string(filepath.ListSeparator) +
			goPath
		tool = "gb"
	}

	return goPath, tool
}

// isGb return the gb package root path if p is gb project directory structure.
func (ctxt *Build) isGb(p string) (string, bool) {
	ctxt.Context = build.Default

	var pkgRoot string
	for {
		pkg, err := ctxt.ImportDir(p, build.IgnoreVendor)
		if err != nil {
			return "", err != nil
			break
		}

		if pkg.Name == "main" {
			pkgRoot = pkg.Dir
			break
		}
		p = filepath.Dir(p)
		continue
	}

	// gb project directory is `../../pkgRoot`
	projRoot, src := filepath.Split(filepath.Dir(pkgRoot))

	manifest := filepath.Join(filepath.Clean(projRoot), "vendor/manifest")
	_, err := os.Stat(manifest)

	return filepath.Clean(projRoot), (err == nil && src == "src")
}

// contextMu Mutex lock for SetContext.
var contextMu sync.Mutex

// SetContext sets the go/build Default.GOPATH and $GOPATH to GoPath(p)
// under a mutex.
// The returned function restores Default.GOPATH to its original value and
// unlocks the mutex.
//
// This function intended to be used to the go/build Default.
func (c *Build) SetContext(p string) func() {
	contextMu.Lock()
	original := build.Default.GOPATH

	build.Default.GOPATH, c.Tool = c.buildContext(p)
	os.Setenv("GOPATH", build.Default.GOPATH)

	return func() {
		build.Default.GOPATH = original
		os.Setenv("GOPATH", build.Default.GOPATH)
		contextMu.Unlock()
	}
}
<|endoftext|>


<commit_before>package bytepool

import (
	"strconv"
	"strings"
	"time"
)

type JsonItem struct {
	*Item
	depth int
	added bool
	pool  *JsonPool
}

func newJsonItem(capacity int, pool *JsonPool) *JsonItem {
	return &JsonItem{
		pool: pool,
		Item: newItem(capacity, nil),
	}
}

var JsonEncode = strings.NewReplacer(`\`, `\\`, `"`, `\"`).Replace

func (item *JsonItem) WriteString(s string) int {
	return item.WriteSafeString(JsonEncode(s))
}

func (item *JsonItem) WriteInt(value int) int {
	n := item.Item.WriteString(strconv.Itoa(value))
	return item.delimit(n)
}

func (item *JsonItem) WriteBool(value bool) int {
	n := item.Item.WriteString(strconv.FormatBool(value))
	return item.delimit(n)
}

func (item *JsonItem) WriteTime(value time.Time) int {
	return item.WriteString(value.Format(time.RFC3339))
}

func (item *JsonItem) WriteSafeString(s string) int {
	return item.writeString(s, true)
}

func (item *JsonItem) WriteKeyString(key, value string) int {
	return item.WriteKeySafeString(key, JsonEncode(value))
}

func (item *JsonItem) WriteKeySafeString(key, value string) int {
	return item.WriteKeyValue(key, `"`+value+`"`)
}

func (item *JsonItem) WriteKeyInt(key string, value int) int {
	return item.WriteKeyValue(key, strconv.Itoa(value))
}

func (item *JsonItem) WriteKeyBool(key string, value bool) int {
	return item.WriteKeyValue(key, strconv.FormatBool(value))
}

func (item *JsonItem) WriteKeyTime(key string, value time.Time) int {
	return item.WriteKeyString(key, value.Format(time.RFC3339))
}

func (item *JsonItem) WriteKeyArray(key string) int {
	n := item.writeString(key, false)
	if item.WriteByte(byte(':')) {
		n++
	}
	if item.BeginArray() {
		n++
	}
	return n
}

func (item *JsonItem) WriteKeyObject(key string) int {
	n := item.writeString(key, false)
	if item.WriteByte(byte(':')) {
		n++
	}
	if item.BeginObject() {
		n++
	}
	return n
}

func (item *JsonItem) WriteKeyValue(key, value string) int {
	n := item.writeString(key, false)
	if item.WriteByte(byte(':')) {
		n++
	}
	n += item.Item.WriteString(value)
	return item.delimit(n)
}

func (item *JsonItem) writeString(s string, delimit bool) int {
	n := item.Item.WriteString(`"` + s + `"`)
	if delimit == false {
		return n
	}
	return item.delimit(n)
}

func (item *JsonItem) BeginArray() bool {
	item.added = false
	item.depth++
	return item.WriteByte('[')
}

func (item *JsonItem) EndArray() (int, error) {
	item.depth--
	item.TrimLastIf(',')
	return item.Write([]byte("],"))
}

func (item *JsonItem) BeginObject() bool {
	item.depth++
	return item.WriteByte('{')
}

func (item *JsonItem) EndObject() (int, error) {
	item.depth--
	item.TrimLastIf(',')
	return item.Write([]byte("},"))
}

func (item *JsonItem) delimit(length int) int {
	if item.depth == 0 {
		return length
	}
	item.WriteByte(',')
	return length + 1
}

func (item *JsonItem) Close() error {
	item.TrimLastIf(',')
	item.Item.Close()
	if item.pool != nil {
		item.depth = 0
		item.pool.list <- item
	}
	return nil
}

func (item *JsonItem) Len() int {
	item.TrimLastIf(',')
	return item.Item.Len()
}

func (item *JsonItem) Bytes() []byte {
	item.TrimLastIf(',')
	return item.Item.Bytes()
}

func (item *JsonItem) Raw() []byte {
	item.TrimLastIf(',')
	return item.Item.Raw()
}

func (item *JsonItem) String() string {
	item.TrimLastIf(',')
	return item.Item.String()
}
<commit_msg>add docs<commit_after>package bytepool

import (
	"strconv"
	"strings"
	"time"
)

// an item specially designed for writing JSON, inherrits Item's behavior
//    depth: indicate nesting level (object & array in json), start at 0
//    added: might be an unnecessary field
//    pool: the pool of json items
type JsonItem struct {
	*Item
	depth int
	added bool
	pool  *JsonPool
}

func newJsonItem(capacity int, pool *JsonPool) *JsonItem {
	return &JsonItem{
		pool: pool,
		Item: newItem(capacity, nil),
	}
}

var JsonEncode = strings.NewReplacer(`\`, `\\`, `"`, `\"`).Replace

// Write arbitary string into the slice
func (item *JsonItem) WriteString(s string) int {
	return item.WriteSafeString(JsonEncode(s))
}

// Write arbitary int into the slice
func (item *JsonItem) WriteInt(value int) int {
	n := item.Item.WriteString(strconv.Itoa(value))
	return item.delimit(n)
}

// Write bool value into the slice
func (item *JsonItem) WriteBool(value bool) int {
	n := item.Item.WriteString(strconv.FormatBool(value))
	return item.delimit(n)
}

// Write time value (in RFC3339, or yyyy-mm-ddThh:mm:ssZ
func (item *JsonItem) WriteTime(value time.Time) int {
	return item.WriteString(value.Format(time.RFC3339))
}

// Write an safe (doesn't need escaping) string into the slice
func (item *JsonItem) WriteSafeString(s string) int {
	return item.writeString(s, true)
}

// Write a key-value pair where value is non-empty string
func (item *JsonItem) WriteKeySafeString(key, value string) int {
	return item.WriteKeyValue(key, `"`+value+`"`)
}

// Write a key-value pair where value is any string (might need escaping)
func (item *JsonItem) WriteKeyString(key, value string) int {
	return item.WriteKeySafeString(key, JsonEncode(value))
}

// Write a key-value pair where value is an int
func (item *JsonItem) WriteKeyInt(key string, value int) int {
	return item.WriteKeyValue(key, strconv.Itoa(value))
}

// Write a key-value pair where value is bool
func (item *JsonItem) WriteKeyBool(key string, value bool) int {
	return item.WriteKeyValue(key, strconv.FormatBool(value))
}

// Write a key-value pair where value is a time (in yyyy-mm-ddThh-mm-ssZ)
func (item *JsonItem) WriteKeyTime(key string, value time.Time) int {
	return item.WriteKeyString(key, value.Format(time.RFC3339))
}

// Write a partial(to `[` char) key-value pair where value is an array
func (item *JsonItem) WriteKeyArray(key string) int {
	n := item.writeString(key, false)
	if item.WriteByte(byte(':')) {
		n++
	}
	if item.BeginArray() {
		n++
	}
	return n
}

// Write a partial(to `{` char) key-value pair where value is an object
func (item *JsonItem) WriteKeyObject(key string) int {
	n := item.writeString(key, false)
	if item.WriteByte(byte(':')) {
		n++
	}
	if item.BeginObject() {
		n++
	}
	return n
}

// Write a key-value pair where value is any string-casted values
func (item *JsonItem) WriteKeyValue(key, value string) int {
	n := item.writeString(key, false)
	if item.WriteByte(byte(':')) {
		n++
	}
	n += item.Item.WriteString(value)
	return item.delimit(n)
}

// Write a string & quotes into the slice.Typically used to write keys
func (item *JsonItem) writeString(s string, delimit bool) int {
	n := item.Item.WriteString(`"` + s + `"`)
	if delimit == false {
		return n
	}
	return item.delimit(n)
}

// Write Array start: `[` into the slice and increase depth
func (item *JsonItem) BeginArray() bool {
	item.added = false
	item.depth++
	return item.WriteByte('[')
}

// Write Array end: `]` into the slice and decrease depth
// also trim extra comma at the end
func (item *JsonItem) EndArray() (int, error) {
	item.depth--
	item.TrimLastIf(',')
	return item.Write([]byte("],"))
}

// Write Object start: `{` into the slice and increase depth
func (item *JsonItem) BeginObject() bool {
	item.depth++
	return item.WriteByte('{')
}

// Write Object start: `}` into the slice and decrease depth
// also trim extra comma at the end
func (item *JsonItem) EndObject() (int, error) {
	item.depth--
	item.TrimLastIf(',')
	return item.Write([]byte("},"))
}

// Write an array / object delimiter: `,` into the slice
func (item *JsonItem) delimit(length int) int {
	if item.depth == 0 {
		return length
	}
	item.WriteByte(',')
	return length + 1
}

// Close the JsonItem and return it to the pool
func (item *JsonItem) Close() error {
	item.TrimLastIf(',')
	item.Item.Close()
	if item.pool != nil {
		item.depth = 0
		item.pool.list <- item
	}
	return nil
}

func (item *JsonItem) Len() int {
	item.TrimLastIf(',')
	return item.Item.Len()
}

// Return the meaningful (intentionally created) bytes in the JsonItem
func (item *JsonItem) Bytes() []byte {
	item.TrimLastIf(',')
	return item.Item.Bytes()
}

// Return the whole slice (including jumble bytes)
func (item *JsonItem) Raw() []byte {
	item.TrimLastIf(',')
	return item.Item.Raw()
}

// Return the string made from the slice
func (item *JsonItem) String() string {
	item.TrimLastIf(',')
	return item.Item.String()
}
<|endoftext|>


<commit_before>package content

import (
	"encoding/json"
	"fmt"
	"regexp"
	"time"

	"github.com/Financial-Times/neo-model-utils-go/mapper"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	log "github.com/Sirupsen/logrus"
	"github.com/jmcvetta/neoism"
)

var uuidExtractRegex = regexp.MustCompile(".*/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$")

// CypherDriver - CypherDriver
type CypherDriver struct {
	cypherRunner neoutils.CypherRunner
	indexManager neoutils.IndexManager
}

//NewCypherDriver instantiate driver
func NewCypherDriver(cypherRunner neoutils.CypherRunner, indexManager neoutils.IndexManager) CypherDriver {
	return CypherDriver{cypherRunner, indexManager}
}

//Initialise initialisation of the indexes
func (pcd CypherDriver) Initialise() error {
	return neoutils.EnsureConstraints(pcd.indexManager, map[string]string{"Content": "uuid"})
}

// Check - Feeds into the Healthcheck and checks whether we can connect to Neo and that the datastore isn't empty
func (pcd CypherDriver) Check() error {
	return neoutils.Check(pcd.cypherRunner)
}

// Read - reads a content given a UUID
func (pcd CypherDriver) Read(uuid string) (interface{}, bool, error) {
	results := []struct {
		content
	}{}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Content {uuid:{uuid}})
			OPTIONAL MATCH (n)-[:IS_CLASSIFIED_BY]->(b:Thing) WITH n,collect({id:b.uuid}) as brands
			return n.uuid as uuid, n.title as title, n.publishedDate as publishedDate, brands`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
		Result: &results,
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return content{}, false, err
	}

	if len(results) == 0 {
		return content{}, false, nil
	}

	result := results[0]

	if len(result.Brands) == 1 && (result.Brands[0].Id == "") {
		result.Brands = []brand{}
	}

	var brands []brand

	for _, brand := range result.Brands {
		brand.Id = mapper.IDURL(brand.Id)
		brands = append(brands, brand)
	}

	c := content{
		UUID:          result.UUID,
		Title:         result.Title,
		PublishedDate: result.PublishedDate,
		Brands:        brands,
	}
	return c, true, nil
}

//Write - Writes a content node
func (pcd CypherDriver) Write(thing interface{}) error {
	c := thing.(content)

	// Only Articles have a body
	if c.Body == "" {
		log.Infof("There is no body with this content item therefore assuming is it not an Article: %s\n", c.UUID)
		return nil
	}
	params := map[string]interface{}{
		"uuid": c.UUID,
	}

	if c.Title != "" {
		params["title"] = c.Title
		params["prefLabel"] = c.Title
	}

	if c.PublishedDate != "" {
		params["publishedDate"] = c.PublishedDate
		datetimeEpoch, err := time.Parse(time.RFC3339, c.PublishedDate)

		if err != nil {
			return err
		}

		params["publishedDateEpoch"] = datetimeEpoch.Unix()
	}

	deleteEntityRelationshipsQuery := &neoism.CypherQuery{
		Statement: `MATCH (t:Thing {uuid:{uuid}})
				MATCH (b:Thing)<-[rel:IS_CLASSIFIED_BY]-(t)
				DELETE rel`,
		Parameters: map[string]interface{}{
			"uuid": c.UUID,
		},
	}

	queries := []*neoism.CypherQuery{deleteEntityRelationshipsQuery}

	for _, brand := range c.Brands {
		brandUuid, err := extractUUIDFromURI(brand.Id)
		if err != nil {
			return err
		}
		addBrandsQuery := addBrandsQuery(brandUuid, c.UUID)
		queries = append(queries, addBrandsQuery)
	}

	statement := `MERGE (n:Thing {uuid: {uuid}})
										set n={allprops}
										set n :Content`

	writeContentQuery := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"uuid":     c.UUID,
			"allprops": params,
		},
	}
	queries = append(queries, writeContentQuery)

	return pcd.cypherRunner.CypherBatch(queries)
}

func addBrandsQuery(brandUuid string, contentUuid string) *neoism.CypherQuery {
	statement := `MATCH (b:Thing{uuid:{brandUuid}})
						MERGE (c:Thing{uuid:{contentUuid}})
						MERGE (c)-[:IS_CLASSIFIED_BY]->(b)`

	query := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"brandUuid":   brandUuid,
			"contentUuid": contentUuid,
		},
	}
	return query
}

func extractUUIDFromURI(uri string) (string, error) {
	result := uuidExtractRegex.FindStringSubmatch(uri)
	if len(result) == 2 {
		return result[1], nil
	}
	return "", fmt.Errorf("Couldn't extract uuid from uri %s", uri)
}

//Delete - Deletes a content
func (pcd CypherDriver) Delete(uuid string) (bool, error) {
	clearNode := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (p)-[rel:IS_CLASSIFIED_BY]->(b:Thing)
			REMOVE p:Content
			DELETE rel
			SET p={props}
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
			"props": map[string]interface{}{
				"uuid": uuid,
			},
		},
		IncludeStats: true,
	}

	removeNodeIfUnused := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (p)-[a]-(x)
			WITH p, count(a) AS relCount
			WHERE relCount = 0
			DELETE p
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{clearNode, removeNodeIfUnused})

	s1, err := clearNode.Stats()
	if err != nil {
		return false, err
	}

	var deleted bool
	if s1.ContainsUpdates && s1.LabelsRemoved > 0 {
		deleted = true
	}

	return deleted, err
}

// DecodeJSON - Decodes JSON into content
func (pcd CypherDriver) DecodeJSON(dec *json.Decoder) (interface{}, string, error) {
	c := content{}
	err := dec.Decode(&c)
	return c, c.UUID, err

}

// Count - Returns a count of the number of content in this Neo instance
func (pcd CypherDriver) Count() (int, error) {

	results := []struct {
		Count int `json:"c"`
	}{}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Content) return count(n) as c`,
		Result:    &results,
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return 0, err
	}

	return results[0].Count, nil
}
<commit_msg>Content with no body returns 204 No Content<commit_after>package content

import (
	"encoding/json"
	"fmt"
	"regexp"
	"time"

	"github.com/Financial-Times/neo-model-utils-go/mapper"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	"github.com/jmcvetta/neoism"
)

var uuidExtractRegex = regexp.MustCompile(".*/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$")

// CypherDriver - CypherDriver
type CypherDriver struct {
	cypherRunner neoutils.CypherRunner
	indexManager neoutils.IndexManager
}

//NewCypherDriver instantiate driver
func NewCypherDriver(cypherRunner neoutils.CypherRunner, indexManager neoutils.IndexManager) CypherDriver {
	return CypherDriver{cypherRunner, indexManager}
}

//Initialise initialisation of the indexes
func (pcd CypherDriver) Initialise() error {
	return neoutils.EnsureConstraints(pcd.indexManager, map[string]string{"Content": "uuid"})
}

// Check - Feeds into the Healthcheck and checks whether we can connect to Neo and that the datastore isn't empty
func (pcd CypherDriver) Check() error {
	return neoutils.Check(pcd.cypherRunner)
}

// Read - reads a content given a UUID
func (pcd CypherDriver) Read(uuid string) (interface{}, bool, error) {
	results := []struct {
		content
	}{}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Content {uuid:{uuid}})
			OPTIONAL MATCH (n)-[:IS_CLASSIFIED_BY]->(b:Thing) WITH n,collect({id:b.uuid}) as brands
			return n.uuid as uuid, n.title as title, n.publishedDate as publishedDate, brands`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
		Result: &results,
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return content{}, false, err
	}

	if len(results) == 0 {
		return content{}, false, nil
	}

	result := results[0]

	if len(result.Brands) == 1 && (result.Brands[0].Id == "") {
		result.Brands = []brand{}
	}

	var brands []brand

	for _, brand := range result.Brands {
		brand.Id = mapper.IDURL(brand.Id)
		brands = append(brands, brand)
	}

	c := content{
		UUID:          result.UUID,
		Title:         result.Title,
		PublishedDate: result.PublishedDate,
		Brands:        brands,
	}
	return c, true, nil
}

//Write - Writes a content node
func (pcd CypherDriver) Write(thing interface{}) error {
	c := thing.(content)

	// Only Articles have a body
	if c.Body == "" {
		return requestError{fmt.Sprintf("There is no body with this content item therefore assuming is it not an Article: %v", c.UUID)}
	}

	params := map[string]interface{}{
		"uuid": c.UUID,
	}

	if c.Title != "" {
		params["title"] = c.Title
		params["prefLabel"] = c.Title
	}

	if c.PublishedDate != "" {
		params["publishedDate"] = c.PublishedDate
		datetimeEpoch, err := time.Parse(time.RFC3339, c.PublishedDate)

		if err != nil {
			return err
		}

		params["publishedDateEpoch"] = datetimeEpoch.Unix()
	}

	deleteEntityRelationshipsQuery := &neoism.CypherQuery{
		Statement: `MATCH (t:Thing {uuid:{uuid}})
				MATCH (b:Thing)<-[rel:IS_CLASSIFIED_BY]-(t)
				DELETE rel`,
		Parameters: map[string]interface{}{
			"uuid": c.UUID,
		},
	}

	queries := []*neoism.CypherQuery{deleteEntityRelationshipsQuery}

	for _, brand := range c.Brands {
		brandUuid, err := extractUUIDFromURI(brand.Id)
		if err != nil {
			return err
		}
		addBrandsQuery := addBrandsQuery(brandUuid, c.UUID)
		queries = append(queries, addBrandsQuery)
	}

	statement := `MERGE (n:Thing {uuid: {uuid}})
										set n={allprops}
										set n :Content`

	writeContentQuery := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"uuid":     c.UUID,
			"allprops": params,
		},
	}
	queries = append(queries, writeContentQuery)

	return pcd.cypherRunner.CypherBatch(queries)
}

func addBrandsQuery(brandUuid string, contentUuid string) *neoism.CypherQuery {
	statement := `MATCH (b:Thing{uuid:{brandUuid}})
						MERGE (c:Thing{uuid:{contentUuid}})
						MERGE (c)-[:IS_CLASSIFIED_BY]->(b)`

	query := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"brandUuid":   brandUuid,
			"contentUuid": contentUuid,
		},
	}
	return query
}

func extractUUIDFromURI(uri string) (string, error) {
	result := uuidExtractRegex.FindStringSubmatch(uri)
	if len(result) == 2 {
		return result[1], nil
	}
	return "", fmt.Errorf("Couldn't extract uuid from uri %s", uri)
}

//Delete - Deletes a content
func (pcd CypherDriver) Delete(uuid string) (bool, error) {
	clearNode := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (p)-[rel:IS_CLASSIFIED_BY]->(b:Thing)
			REMOVE p:Content
			DELETE rel
			SET p={props}
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
			"props": map[string]interface{}{
				"uuid": uuid,
			},
		},
		IncludeStats: true,
	}

	removeNodeIfUnused := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (p)-[a]-(x)
			WITH p, count(a) AS relCount
			WHERE relCount = 0
			DELETE p
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{clearNode, removeNodeIfUnused})

	s1, err := clearNode.Stats()
	if err != nil {
		return false, err
	}

	var deleted bool
	if s1.ContainsUpdates && s1.LabelsRemoved > 0 {
		deleted = true
	}

	return deleted, err
}

// DecodeJSON - Decodes JSON into content
func (pcd CypherDriver) DecodeJSON(dec *json.Decoder) (interface{}, string, error) {
	c := content{}
	err := dec.Decode(&c)
	return c, c.UUID, err

}

// Count - Returns a count of the number of content in this Neo instance
func (pcd CypherDriver) Count() (int, error) {

	results := []struct {
		Count int `json:"c"`
	}{}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Content) return count(n) as c`,
		Result:    &results,
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return 0, err
	}

	return results[0].Count, nil
}

type requestError struct {
	details string
}

func (re requestError) Error() string {
	return "No Content"
}

func (re requestError) NoContentReturnedDetails() string {
	return re.details
}
<|endoftext|>


<commit_before>package content

import (
	"encoding/json"
	"fmt"
	"regexp"
	"time"

	"github.com/Financial-Times/neo-model-utils-go/mapper"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	"github.com/jmcvetta/neoism"
)

var uuidExtractRegex = regexp.MustCompile(".*/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$")

// CypherDriver - CypherDriver
type CypherDriver struct {
	cypherRunner neoutils.CypherRunner
	indexManager neoutils.IndexManager
}

//NewCypherDriver instantiate driver
func NewCypherDriver(cypherRunner neoutils.CypherRunner, indexManager neoutils.IndexManager) CypherDriver {
	return CypherDriver{cypherRunner, indexManager}
}

//Initialise initialisation of the indexes
func (pcd CypherDriver) Initialise() error {
	return neoutils.EnsureConstraints(pcd.indexManager, map[string]string{"Content": "uuid"})
}

// Check - Feeds into the Healthcheck and checks whether we can connect to Neo and that the datastore isn't empty
func (pcd CypherDriver) Check() error {
	return neoutils.Check(pcd.cypherRunner)
}

// Read - reads a content given a UUID
func (pcd CypherDriver) Read(uuid string) (interface{}, bool, error) {
	results := []struct {
		content
	}{}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Content {uuid:{uuid}})
			OPTIONAL MATCH (n)-[:IS_CLASSIFIED_BY]->(b:Thing) WITH n,collect({id:b.uuid}) as brands
			return n.uuid as uuid, n.title as title, n.publishedDate as publishedDate, brands`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
		Result: &results,
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return content{}, false, err
	}

	if len(results) == 0 {
		return content{}, false, nil
	}

	result := results[0]

	if len(result.Brands) == 1 && (result.Brands[0].Id == "") {
		result.Brands = []brand{}
	}

	var brands []brand

	for _, brand := range result.Brands {
		brand.Id = mapper.IDURL(brand.Id)
		brands = append(brands, brand)
	}

	c := content{
		UUID:          result.UUID,
		Title:         result.Title,
		PublishedDate: result.PublishedDate,
		Brands:        brands,
	}
	return c, true, nil
}

//Write - Writes a content node
func (pcd CypherDriver) Write(thing interface{}) error {
	c := thing.(content)

	// Only Articles have a body
	if c.Body == "" {
		return requestError{fmt.Sprintf("There is no body with this content item therefore assuming is it not an Article: %v", c.UUID)}
	}

	params := map[string]interface{}{
		"uuid": c.UUID,
	}

	if c.Title != "" {
		params["title"] = c.Title
		params["prefLabel"] = c.Title
	}

	if c.PublishedDate != "" {
		params["publishedDate"] = c.PublishedDate
		datetimeEpoch, err := time.Parse(time.RFC3339, c.PublishedDate)

		if err != nil {
			return err
		}

		params["publishedDateEpoch"] = datetimeEpoch.Unix()
	}

	deleteEntityRelationshipsQuery := &neoism.CypherQuery{
		Statement: `MATCH (t:Thing {uuid:{uuid}})
				MATCH (b:Thing)<-[rel:IS_CLASSIFIED_BY]-(t)
				DELETE rel`,
		Parameters: map[string]interface{}{
			"uuid": c.UUID,
		},
	}

	queries := []*neoism.CypherQuery{deleteEntityRelationshipsQuery}

	for _, brand := range c.Brands {
		brandUuid, err := extractUUIDFromURI(brand.Id)
		if err != nil {
			return err
		}
		addBrandsQuery := addBrandsQuery(brandUuid, c.UUID)
		queries = append(queries, addBrandsQuery)
	}

	statement := `MERGE (n:Thing {uuid: {uuid}})
										set n={allprops}
										set n :Content`

	writeContentQuery := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"uuid":     c.UUID,
			"allprops": params,
		},
	}
	queries = append(queries, writeContentQuery)

	return pcd.cypherRunner.CypherBatch(queries)
}

func addBrandsQuery(brandUuid string, contentUuid string) *neoism.CypherQuery {
	statement := `MERGE (b:Thing{uuid:{brandUuid}})
						MERGE (c:Thing{uuid:{contentUuid}})
						MERGE (c)-[rel:IS_CLASSIFIED_BY{platformVersion:{platformVersion}}]->(b)`

	query := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"brandUuid":       brandUuid,
			"contentUuid":     contentUuid,
			"platformVersion": platformVersion,
		},
	}
	return query
}

func extractUUIDFromURI(uri string) (string, error) {
	result := uuidExtractRegex.FindStringSubmatch(uri)
	if len(result) == 2 {
		return result[1], nil
	}
	return "", fmt.Errorf("Couldn't extract uuid from uri %s", uri)
}

//Delete - Deletes a content
func (pcd CypherDriver) Delete(uuid string) (bool, error) {
	clearNode := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (p)-[rel:IS_CLASSIFIED_BY]->(b:Thing)
			REMOVE p:Content
			DELETE rel
			SET p={props}
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
			"props": map[string]interface{}{
				"uuid": uuid,
			},
		},
		IncludeStats: true,
	}

	removeNodeIfUnused := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (p)-[a]-(x)
			WITH p, count(a) AS relCount
			WHERE relCount = 0
			DELETE p
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{clearNode, removeNodeIfUnused})

	s1, err := clearNode.Stats()
	if err != nil {
		return false, err
	}

	var deleted bool
	if s1.ContainsUpdates && s1.LabelsRemoved > 0 {
		deleted = true
	}

	return deleted, err
}

// DecodeJSON - Decodes JSON into content
func (pcd CypherDriver) DecodeJSON(dec *json.Decoder) (interface{}, string, error) {
	c := content{}
	err := dec.Decode(&c)
	return c, c.UUID, err

}

// Count - Returns a count of the number of content in this Neo instance
func (pcd CypherDriver) Count() (int, error) {

	results := []struct {
		Count int `json:"c"`
	}{}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Content) return count(n) as c`,
		Result:    &results,
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return 0, err
	}

	return results[0].Count, nil
}

type requestError struct {
	details string
}

func (re requestError) Error() string {
	return "No Content"
}

func (re requestError) NoContentReturnedDetails() string {
	return re.details
}

const (
	platformVersion = "v2"
)
<commit_msg>Changed the error to log message for no body<commit_after>package content

import (
	"encoding/json"
	"fmt"
	"regexp"
	"time"

	"github.com/Financial-Times/neo-model-utils-go/mapper"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	log "github.com/Sirupsen/logrus"
	"github.com/jmcvetta/neoism"
)

var uuidExtractRegex = regexp.MustCompile(".*/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$")

// CypherDriver - CypherDriver
type CypherDriver struct {
	cypherRunner neoutils.CypherRunner
	indexManager neoutils.IndexManager
}

//NewCypherDriver instantiate driver
func NewCypherDriver(cypherRunner neoutils.CypherRunner, indexManager neoutils.IndexManager) CypherDriver {
	return CypherDriver{cypherRunner, indexManager}
}

//Initialise initialisation of the indexes
func (pcd CypherDriver) Initialise() error {
	return neoutils.EnsureConstraints(pcd.indexManager, map[string]string{"Content": "uuid"})
}

// Check - Feeds into the Healthcheck and checks whether we can connect to Neo and that the datastore isn't empty
func (pcd CypherDriver) Check() error {
	return neoutils.Check(pcd.cypherRunner)
}

// Read - reads a content given a UUID
func (pcd CypherDriver) Read(uuid string) (interface{}, bool, error) {
	results := []struct {
		content
	}{}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Content {uuid:{uuid}})
			OPTIONAL MATCH (n)-[:IS_CLASSIFIED_BY]->(b:Thing) WITH n,collect({id:b.uuid}) as brands
			return n.uuid as uuid, n.title as title, n.publishedDate as publishedDate, brands`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
		Result: &results,
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return content{}, false, err
	}

	if len(results) == 0 {
		return content{}, false, nil
	}

	result := results[0]

	if len(result.Brands) == 1 && (result.Brands[0].Id == "") {
		result.Brands = []brand{}
	}

	var brands []brand

	for _, brand := range result.Brands {
		brand.Id = mapper.IDURL(brand.Id)
		brands = append(brands, brand)
	}

	c := content{
		UUID:          result.UUID,
		Title:         result.Title,
		PublishedDate: result.PublishedDate,
		Brands:        brands,
	}
	return c, true, nil
}

//Write - Writes a content node
func (pcd CypherDriver) Write(thing interface{}) error {
	c := thing.(content)

	// Only Articles have a body
	if c.Body == "" {
		log.Infof("There is no body with this content item therefore assuming is it not an Article: %v", c.UUID)
		return nil
	}

	params := map[string]interface{}{
		"uuid": c.UUID,
	}

	if c.Title != "" {
		params["title"] = c.Title
		params["prefLabel"] = c.Title
	}

	if c.PublishedDate != "" {
		params["publishedDate"] = c.PublishedDate
		datetimeEpoch, err := time.Parse(time.RFC3339, c.PublishedDate)

		if err != nil {
			return err
		}

		params["publishedDateEpoch"] = datetimeEpoch.Unix()
	}

	deleteEntityRelationshipsQuery := &neoism.CypherQuery{
		Statement: `MATCH (t:Thing {uuid:{uuid}})
				MATCH (b:Thing)<-[rel:IS_CLASSIFIED_BY]-(t)
				DELETE rel`,
		Parameters: map[string]interface{}{
			"uuid": c.UUID,
		},
	}

	queries := []*neoism.CypherQuery{deleteEntityRelationshipsQuery}

	for _, brand := range c.Brands {
		brandUuid, err := extractUUIDFromURI(brand.Id)
		if err != nil {
			return err
		}
		addBrandsQuery := addBrandsQuery(brandUuid, c.UUID)
		queries = append(queries, addBrandsQuery)
	}

	statement := `MERGE (n:Thing {uuid: {uuid}})
										set n={allprops}
										set n :Content`

	writeContentQuery := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"uuid":     c.UUID,
			"allprops": params,
		},
	}
	queries = append(queries, writeContentQuery)

	return pcd.cypherRunner.CypherBatch(queries)
}

func addBrandsQuery(brandUuid string, contentUuid string) *neoism.CypherQuery {
	statement := `MERGE (b:Thing{uuid:{brandUuid}})
						MERGE (c:Thing{uuid:{contentUuid}})
						MERGE (c)-[rel:IS_CLASSIFIED_BY{platformVersion:{platformVersion}}]->(b)`

	query := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"brandUuid":       brandUuid,
			"contentUuid":     contentUuid,
			"platformVersion": platformVersion,
		},
	}
	return query
}

func extractUUIDFromURI(uri string) (string, error) {
	result := uuidExtractRegex.FindStringSubmatch(uri)
	if len(result) == 2 {
		return result[1], nil
	}
	return "", fmt.Errorf("Couldn't extract uuid from uri %s", uri)
}

//Delete - Deletes a content
func (pcd CypherDriver) Delete(uuid string) (bool, error) {
	clearNode := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (p)-[rel:IS_CLASSIFIED_BY]->(b:Thing)
			REMOVE p:Content
			DELETE rel
			SET p={props}
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
			"props": map[string]interface{}{
				"uuid": uuid,
			},
		},
		IncludeStats: true,
	}

	removeNodeIfUnused := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (p)-[a]-(x)
			WITH p, count(a) AS relCount
			WHERE relCount = 0
			DELETE p
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{clearNode, removeNodeIfUnused})

	s1, err := clearNode.Stats()
	if err != nil {
		return false, err
	}

	var deleted bool
	if s1.ContainsUpdates && s1.LabelsRemoved > 0 {
		deleted = true
	}

	return deleted, err
}

// DecodeJSON - Decodes JSON into content
func (pcd CypherDriver) DecodeJSON(dec *json.Decoder) (interface{}, string, error) {
	c := content{}
	err := dec.Decode(&c)
	return c, c.UUID, err

}

// Count - Returns a count of the number of content in this Neo instance
func (pcd CypherDriver) Count() (int, error) {

	results := []struct {
		Count int `json:"c"`
	}{}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Content) return count(n) as c`,
		Result:    &results,
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return 0, err
	}

	return results[0].Count, nil
}

const (
	platformVersion = "v2"
)
<|endoftext|>


<commit_before>package engine

import (
	"sync"
	"testing"

	"github.com/stretchr/testify/require"
)

func Test_Scene_Locate(t *testing.T) {
	scene := &Scene{
		area: Area{
			width:  100,
			height: 100,
		},
		locations:      []Location{},
		locationsMutex: &sync.RWMutex{},
	}

	location := Location{Dot{1, 1}, Dot{1, 2}}
	err := scene.Locate(location)
	require.Nil(t, err)
	require.Equal(t, []Location{location}, scene.locations)
}

func Benchmark_Scene_Locate(b *testing.B) {
	// TODO: Implement benchmark.
}

func Benchmark_Scene_Relocate(b *testing.B) {
	// TODO: Implement benchmark.
}

func Test_Scene_LocateRandomRect(t *testing.T) {
	scene := &Scene{
		area: Area{
			width:  100,
			height: 100,
		},
		locations:      []Location{},
		locationsMutex: &sync.RWMutex{},
	}

	location, err := scene.LocateRandomRect(1, 5)
	require.Nil(t, err)
	require.Equal(t, []Location{location}, scene.locations)
}

func Test_Scene_LocateAvailableDots_EmptyScene(t *testing.T) {
	scene := &Scene{
		area: Area{
			width:  100,
			height: 100,
		},
		locations:      []Location{},
		locationsMutex: &sync.RWMutex{},
	}

	location := Location{Dot{1, 1}, Dot{1, 2}}
	locationActual := scene.LocateAvailableDots(location)
	require.Equal(t, []Location{location}, scene.locations)
	require.Equal(t, []Location{locationActual}, scene.locations)
	require.Equal(t, location, locationActual)
}

func Test_Scene_LocateAvailableDots_LocationNotAvailable(t *testing.T) {
	scene := &Scene{
		area: Area{
			width:  100,
			height: 100,
		},
		locations: []Location{
			{Dot{1, 1}, Dot{1, 2}},
		},
		locationsMutex: &sync.RWMutex{},
	}

	location := Location{Dot{1, 1}, Dot{1, 2}}
	locationActual := scene.LocateAvailableDots(location)
	require.Equal(t, Location{}, locationActual)
}

func Test_Scene_LocateAvailableDots_LocationsIntersects(t *testing.T) {
	scene := &Scene{
		area: Area{
			width:  100,
			height: 100,
		},
		locations: []Location{
			{Dot{1, 1}, Dot{1, 2}, Dot{1, 3}, Dot{1, 4}},
		},
		locationsMutex: &sync.RWMutex{},
	}

	location := Location{Dot{1, 1}, Dot{1, 0}}
	locationActual := scene.LocateAvailableDots(location)
	require.Equal(t, Location{Dot{1, 0}}, locationActual)
	require.Equal(t, []Location{
		{Dot{1, 1}, Dot{1, 2}, Dot{1, 3}, Dot{1, 4}},
		{Dot{1, 0}},
	}, scene.locations)
}
<commit_msg>Create tests for engine.Scene<commit_after>package engine

import (
	"sync"
	"testing"

	"github.com/stretchr/testify/require"
)

func Test_Scene_Locate_SquareScene(t *testing.T) {
	scene := &Scene{
		area: Area{
			width:  100,
			height: 100,
		},
		locations:      []Location{},
		locationsMutex: &sync.RWMutex{},
	}

	location := Location{Dot{1, 1}, Dot{1, 2}}
	err := scene.Locate(location)
	require.Nil(t, err)
	require.Equal(t, []Location{location}, scene.locations)
}

func Test_Scene_Locate_RectScene(t *testing.T) {
	scene := &Scene{
		area: Area{
			width:  100,
			height: 200,
		},
		locations:      []Location{},
		locationsMutex: &sync.RWMutex{},
	}

	location := Location{Dot{1, 1}, Dot{1, 2}}
	err := scene.Locate(location)
	require.Nil(t, err)
	require.Equal(t, []Location{location}, scene.locations)
}

func Benchmark_Scene_Locate(b *testing.B) {
	scene := func() *Scene {
		return &Scene{
			area: Area{
				width:  100,
				height: 100,
			},
			locations: []Location{
				{
					{0, 1},
					{0, 2},
					{0, 3},
					{0, 4},
					{0, 5},
					{0, 6},
				},
				{
					{4, 1},
					{4, 2},
					{4, 3},
					{4, 4},
					{4, 5},
					{4, 6},
				},
				{
					{5, 2},
					{6, 2},
					{7, 2},
					{8, 2},
					{9, 2},
					{10, 2},
				},
			},
			locationsMutex: &sync.RWMutex{},
		}
	}

	for n := 0; n < b.N; n++ {
		scene().Locate(Location{
			{10, 11},
			{10, 12},
			{10, 13},
			{10, 14},
			{10, 15},
			{10, 16},
			{10, 17},
			{10, 18},
			{10, 19},
			{10, 20},
			{10, 21},
		})
	}
}

func Benchmark_Scene_Relocate(b *testing.B) {
	// TODO: Implement benchmark.
}

func Test_Scene_LocateRandomRect_SquareScene(t *testing.T) {
	scene := &Scene{
		area: Area{
			width:  100,
			height: 100,
		},
		locations:      []Location{},
		locationsMutex: &sync.RWMutex{},
	}

	location, err := scene.LocateRandomRect(1, 5)
	require.Nil(t, err)
	require.Equal(t, []Location{location}, scene.locations)
}

func Test_Scene_LocateRandomRect_RectScene(t *testing.T) {
	scene := &Scene{
		area: Area{
			width:  150,
			height: 99,
		},
		locations:      []Location{},
		locationsMutex: &sync.RWMutex{},
	}

	location, err := scene.LocateRandomRect(1, 5)
	require.Nil(t, err)
	require.Equal(t, []Location{location}, scene.locations)
}

func Test_Scene_LocateAvailableDots_EmptySquareScene(t *testing.T) {
	scene := &Scene{
		area: Area{
			width:  100,
			height: 100,
		},
		locations:      []Location{},
		locationsMutex: &sync.RWMutex{},
	}

	location := Location{Dot{1, 1}, Dot{1, 2}}
	locationActual := scene.LocateAvailableDots(location)
	require.Equal(t, []Location{location}, scene.locations)
	require.Equal(t, []Location{locationActual}, scene.locations)
	require.Equal(t, location, locationActual)
}

func Test_Scene_LocateAvailableDots_LocationNotAvailable(t *testing.T) {
	scene := &Scene{
		area: Area{
			width:  100,
			height: 100,
		},
		locations: []Location{
			{Dot{1, 1}, Dot{1, 2}},
		},
		locationsMutex: &sync.RWMutex{},
	}

	location := Location{Dot{1, 1}, Dot{1, 2}}
	locationActual := scene.LocateAvailableDots(location)
	require.Equal(t, Location{}, locationActual)
}

func Test_Scene_LocateAvailableDots_LocationsIntersects(t *testing.T) {
	scene := &Scene{
		area: Area{
			width:  100,
			height: 100,
		},
		locations: []Location{
			{Dot{1, 1}, Dot{1, 2}, Dot{1, 3}, Dot{1, 4}},
		},
		locationsMutex: &sync.RWMutex{},
	}

	location := Location{Dot{1, 1}, Dot{1, 0}}
	locationActual := scene.LocateAvailableDots(location)
	require.Equal(t, Location{Dot{1, 0}}, locationActual)
	require.Equal(t, []Location{
		{Dot{1, 1}, Dot{1, 2}, Dot{1, 3}, Dot{1, 4}},
		{Dot{1, 0}},
	}, scene.locations)
}
<|endoftext|>


<commit_before>// Copyright 2016 Volker Dobler.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package suite

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"strconv"
	"time"

	"github.com/vdobler/ht/cookiejar"
	"github.com/vdobler/ht/ht"
)

// A Suite is a collection of Tests which can be executed sequentily with the
// result captured.
type Suite struct {
	Name        string // Name of the Suite.
	Description string // Description of what's going on here.
	KeepCookies bool   // KeepCookies in a cookie jar common to all Tests.

	Status   ht.Status     // Status is the overall status of the whole suite.
	Error    error         // Error encountered during execution of the suite.
	Started  time.Time     // Start of the execution.
	Duration time.Duration // Duration of the execution.

	Tests []*ht.Test // The Tests to execute

	Variables      map[string]string // The inital variable assignemnt
	FinalVariables map[string]string // The final set of variables.
	Jar            *cookiejar.Jar    // The cookie jar used
	Log            *log.Logger       // The logger used.

	scope map[string]string
	tests []*RawTest
}

func shouldRun(t int, rs *RawSuite, s *Suite) bool {
	if !rs.tests[t].IsEnabled() {
		return false
	}

	// Stop execution on errors during setup
	for i := 0; i < len(rs.Setup) && i < len(s.Tests); i++ {
		if s.Tests[i].Status > ht.Pass {
			return false
		}
	}
	return true
}

func newScope(outer, inner map[string]string, auto bool) map[string]string {
	// 1. Copy of outer scope
	scope := make(map[string]string, len(outer)+len(inner)+2)
	for gn, gv := range outer {
		scope[gn] = gv
	}
	if auto {
		scope["COUNTER"] = strconv.Itoa(<-GetCounter)
		scope["RANDOM"] = strconv.Itoa(100000 + ht.RandomIntn(900000))
	}
	replacer := varReplacer(scope)

	// 2. Merging inner defaults, allow substitutions from outer scope
	for name, val := range inner {
		if _, ok := scope[name]; ok {
			// Variable name exists in outer scope, do not
			// overwrite with suite defaults.
			continue
		}
		scope[name] = replacer.Replace(val)
	}

	return scope
}

// NewFromRaw sets up a new Suite from rs, read to be Iterated.
func NewFromRaw(rs *RawSuite, global map[string]string, jar *cookiejar.Jar, logger *log.Logger) *Suite {
	// Create cookie jar if needed.
	if rs.KeepCookies {
		if jar == nil {
			// Make own, private-use jar.
			jar, _ = cookiejar.New(nil)
		}
	} else {
		jar = nil
	}

	if logger == nil {
		logger = log.New(ioutil.Discard, "", 0)
	}

	suite := &Suite{
		KeepCookies: rs.KeepCookies,

		Status: ht.NotRun,
		Error:  nil,

		Tests: make([]*ht.Test, 0, len(rs.tests)),

		Variables:      make(map[string]string),
		FinalVariables: make(map[string]string),
		Jar:            jar,
		Log:            logger,
		tests:          rs.tests,
	}

	suite.scope = newScope(global, rs.Variables, true)
	suite.scope["SUITE_DIR"] = rs.File.Dirname()
	suite.scope["SUITE_NAME"] = rs.File.Basename()
	replacer := varReplacer(suite.scope)

	suite.Name = replacer.Replace(rs.Name)
	suite.Description = replacer.Replace(rs.Description)

	for n, v := range suite.scope {
		suite.Variables[n] = v
	}

	return suite
}

// An executor is responsible for executing the given test during the
// Iterate'ion of a Suite. It should return nil if execution should continue
// and Err{Abort,Skip}Execution to stop further iteration.
type Executor func(test *ht.Test) error

var (
	ErrAbortExecution = errors.New("Abort Execution")
	ErrSkipExecution  = errors.New("Skip further Tests")
)

// Iterate the suite through the given executor.
func (suite *Suite) Iterate(executor Executor) {
	now := time.Now()
	now = now.Add(-time.Duration(now.Nanosecond()))
	suite.Started = now

	var exstat error
	overall := ht.NotRun
	errors := ht.ErrorList{}

	for _, rt := range suite.tests {
		// suite.Log.Printf("Executing Test %q\n", rt.File.Name)
		callScope := newScope(suite.scope, rt.contextVars, true)
		testScope := newScope(callScope, rt.Variables, false)
		testScope["TEST_DIR"] = rt.File.Dirname()
		testScope["TEST_NAME"] = rt.File.Basename()
		test, err := rt.ToTest(testScope)
		if err != nil {
			test.Status = ht.Bogus
			test.Error = err
		}
		if exstat == ErrSkipExecution {
			test.Status = ht.Skipped
		}

		test.Jar = suite.Jar
		// test.Execution.Verbosity = rs.Verbosity
		test.Log = suite.Log

		exstat = executor(test)
		if test.Status == ht.Pass {
			suite.updateVariables(test)
		}

		suite.Tests = append(suite.Tests, test)
		if test.Status > overall {
			overall = test.Status
		}
		if err := test.Error; err != nil {
			errors = append(errors, err)
		}

		if exstat == ErrAbortExecution {
			break
		}
	}
	suite.Duration = time.Since(suite.Started)
	clip := suite.Duration.Nanoseconds() % 1000000
	suite.Duration -= time.Duration(clip)
	suite.Status = overall
	if len(errors) == 0 {
		suite.Error = nil
	} else {
		suite.Error = errors
	}

	for n, v := range suite.scope {
		suite.FinalVariables[n] = v
	}
}

func (suite *Suite) updateVariables(test *ht.Test) {
	if test.Status != ht.Pass {
		return
	}

	for varname, value := range test.Extract() {
		if old, ok := suite.scope[varname]; ok {
			if value != old {
				suite.Log.Printf("Updating variable %q to %q\n",
					varname, value)
			} else {
				suite.Log.Printf("Keeping  variable %q as %q\n",
					varname, value)
			}
		} else {
			suite.Log.Printf("Setting  variable %q to %q\n",
				varname, value)
		}
		suite.scope[varname] = value
	}
}

func (suite *Suite) updateStatus(test *ht.Test) {
	if test.Status <= suite.Status {
		return
	}

	suite.Status = test.Status
	if test.Error != nil {
		suite.Error = test.Error
	}
}

// Stats counts the test results of s.
func (suite *Suite) Stats() (notRun int, skipped int, passed int, failed int, errored int, bogus int) {
	for _, tr := range suite.Tests {
		switch tr.Status {
		case ht.NotRun:
			notRun++
		case ht.Skipped:
			skipped++
		case ht.Pass:
			passed++
		case ht.Fail:
			failed++
		case ht.Error:
			errored++
		case ht.Bogus:
			bogus++
		default:
			panic(fmt.Sprintf("No such Status %d in suite %q test %q",
				tr.Status, suite.Name, tr.Name))
		}
	}
	return
}
<commit_msg>suite: fix skipping main tests<commit_after>// Copyright 2016 Volker Dobler.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package suite

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"strconv"
	"time"

	"github.com/vdobler/ht/cookiejar"
	"github.com/vdobler/ht/ht"
)

// A Suite is a collection of Tests which can be executed sequentily with the
// result captured.
type Suite struct {
	Name        string // Name of the Suite.
	Description string // Description of what's going on here.
	KeepCookies bool   // KeepCookies in a cookie jar common to all Tests.

	Status   ht.Status     // Status is the overall status of the whole suite.
	Error    error         // Error encountered during execution of the suite.
	Started  time.Time     // Start of the execution.
	Duration time.Duration // Duration of the execution.

	Tests []*ht.Test // The Tests to execute

	Variables      map[string]string // The inital variable assignemnt
	FinalVariables map[string]string // The final set of variables.
	Jar            *cookiejar.Jar    // The cookie jar used
	Log            *log.Logger       // The logger used.

	scope map[string]string
	tests []*RawTest
}

func shouldRun(t int, rs *RawSuite, s *Suite) bool {
	if !rs.tests[t].IsEnabled() {
		return false
	}

	// Stop execution on errors during setup
	for i := 0; i < len(rs.Setup) && i < len(s.Tests); i++ {
		if s.Tests[i].Status > ht.Pass {
			return false
		}
	}
	return true
}

func newScope(outer, inner map[string]string, auto bool) map[string]string {
	// 1. Copy of outer scope
	scope := make(map[string]string, len(outer)+len(inner)+2)
	for gn, gv := range outer {
		scope[gn] = gv
	}
	if auto {
		scope["COUNTER"] = strconv.Itoa(<-GetCounter)
		scope["RANDOM"] = strconv.Itoa(100000 + ht.RandomIntn(900000))
	}
	replacer := varReplacer(scope)

	// 2. Merging inner defaults, allow substitutions from outer scope
	for name, val := range inner {
		if _, ok := scope[name]; ok {
			// Variable name exists in outer scope, do not
			// overwrite with suite defaults.
			continue
		}
		scope[name] = replacer.Replace(val)
	}

	return scope
}

// NewFromRaw sets up a new Suite from rs, read to be Iterated.
func NewFromRaw(rs *RawSuite, global map[string]string, jar *cookiejar.Jar, logger *log.Logger) *Suite {
	// Create cookie jar if needed.
	if rs.KeepCookies {
		if jar == nil {
			// Make own, private-use jar.
			jar, _ = cookiejar.New(nil)
		}
	} else {
		jar = nil
	}

	if logger == nil {
		logger = log.New(ioutil.Discard, "", 0)
	}

	suite := &Suite{
		KeepCookies: rs.KeepCookies,

		Status: ht.NotRun,
		Error:  nil,

		Tests: make([]*ht.Test, 0, len(rs.tests)),

		Variables:      make(map[string]string),
		FinalVariables: make(map[string]string),
		Jar:            jar,
		Log:            logger,
		tests:          rs.tests,
	}

	suite.scope = newScope(global, rs.Variables, true)
	suite.scope["SUITE_DIR"] = rs.File.Dirname()
	suite.scope["SUITE_NAME"] = rs.File.Basename()
	replacer := varReplacer(suite.scope)

	suite.Name = replacer.Replace(rs.Name)
	suite.Description = replacer.Replace(rs.Description)

	for n, v := range suite.scope {
		suite.Variables[n] = v
	}

	return suite
}

// An executor is responsible for executing the given test during the
// Iterate'ion of a Suite. It should return nil if execution should continue
// and Err{Abort,Skip}Execution to stop further iteration.
type Executor func(test *ht.Test) error

var (
	ErrAbortExecution = errors.New("Abort Execution")
	ErrSkipExecution  = errors.New("Skip further Tests")
)

// Iterate the suite through the given executor.
func (suite *Suite) Iterate(executor Executor) {
	now := time.Now()
	now = now.Add(-time.Duration(now.Nanosecond()))
	suite.Started = now

	var exstat error
	overall := ht.NotRun
	errors := ht.ErrorList{}

	for _, rt := range suite.tests {
		// suite.Log.Printf("Executing Test %q\n", rt.File.Name)
		callScope := newScope(suite.scope, rt.contextVars, true)
		testScope := newScope(callScope, rt.Variables, false)
		testScope["TEST_DIR"] = rt.File.Dirname()
		testScope["TEST_NAME"] = rt.File.Basename()
		test, err := rt.ToTest(testScope)
		if err != nil {
			test.Status = ht.Bogus
			test.Error = err
		}
		test.Jar = suite.Jar
		test.Log = suite.Log

		if exstat == ErrSkipExecution {
			test.Status = ht.Skipped
		}

		newexstat := executor(test)
		if test.Status == ht.Pass {
			suite.updateVariables(test)
		}

		if exstat != ErrSkipExecution {
			exstat = newexstat
		}

		suite.Tests = append(suite.Tests, test)
		if test.Status > overall {
			overall = test.Status
		}
		if err := test.Error; err != nil {
			errors = append(errors, err)
		}

		if exstat == ErrAbortExecution {
			break
		}
	}
	suite.Duration = time.Since(suite.Started)
	clip := suite.Duration.Nanoseconds() % 1000000
	suite.Duration -= time.Duration(clip)
	suite.Status = overall
	if len(errors) == 0 {
		suite.Error = nil
	} else {
		suite.Error = errors
	}

	for n, v := range suite.scope {
		suite.FinalVariables[n] = v
	}
}

func (suite *Suite) updateVariables(test *ht.Test) {
	if test.Status != ht.Pass {
		return
	}

	for varname, value := range test.Extract() {
		if old, ok := suite.scope[varname]; ok {
			if value != old {
				suite.Log.Printf("Updating variable %q to %q\n",
					varname, value)
			} else {
				suite.Log.Printf("Keeping  variable %q as %q\n",
					varname, value)
			}
		} else {
			suite.Log.Printf("Setting  variable %q to %q\n",
				varname, value)
		}
		suite.scope[varname] = value
	}
}

func (suite *Suite) updateStatus(test *ht.Test) {
	if test.Status <= suite.Status {
		return
	}

	suite.Status = test.Status
	if test.Error != nil {
		suite.Error = test.Error
	}
}

// Stats counts the test results of s.
func (suite *Suite) Stats() (notRun int, skipped int, passed int, failed int, errored int, bogus int) {
	for _, tr := range suite.Tests {
		switch tr.Status {
		case ht.NotRun:
			notRun++
		case ht.Skipped:
			skipped++
		case ht.Pass:
			passed++
		case ht.Fail:
			failed++
		case ht.Error:
			errored++
		case ht.Bogus:
			bogus++
		default:
			panic(fmt.Sprintf("No such Status %d in suite %q test %q",
				tr.Status, suite.Name, tr.Name))
		}
	}
	return
}
<|endoftext|>