<commit_before>package gomog

import( "net"
  "errors"
  "sync"
  "strconv"
  "strings"
  "time"
  "net/url"
  "math/rand"
  "regexp"
  )

var errRegex *regexp.Regexp = regexp.MustCompile( "^ERR\\s+(\\w+)\\s*([^\\r\\n]*)" )
var successRegex *regexp.Regexp = regexp.MustCompile( "^OK\\s+\\d*\\s*(\\S*)" )
var newlineRegex *regexp.Regexp = regexp.MustCompile( "\r?\n\\z" )

var SocketError error = errors.New( "Socket error!" )
var SocketReadError error = errors.New( "Socket read error!" )
var TruncatedRequestError error = errors.New( "Request sent was unexpectedly truncated" )
var InvalidResponseError error = errors.New( "Response was not valid!" )
var NotImplementedError error = errors.New( "That command is not implemented yet" )

type MogileHost struct {
  Name string
  AlternateMask *string
  AlternateIp *net.IPAddr
  IpAddress *net.IPAddr
  Id int
  Status string
  Port int
  GetPort *int
}
type MogileFid struct {
  DeviceId int
  Class string
  FileId int
  DevCount int
  Size int64
  Key string
  Domain string
  Paths []string
}
type MogileDevice struct {
  mbAsof string
  HostId int
  SpaceUsed int
  SpaceFree int
  SpaceTotal int
  Id int
  Status string
  State string
  Weight int
  Utilization string
  RejectBadMd5 bool
}
type MogileClient struct {
  sync.Mutex
  Hosts []*net.TCPAddr
  Timeout time.Duration
  Socket *net.TCPConn
  LastError string
  LastErrorMessage string
}
func New( hosts []string ) ( *MogileClient, error ) {
  addresses := make( []*net.TCPAddr, 0 )
  for i, _ := range( hosts ) {
    address, err := net.ResolveTCPAddr( "tcp", hosts[i] );
    if( err != nil ) {
      return nil, errors.New( "Could not read as address: " + hosts[i] );
    }
    addresses = append( addresses, address )
  }
  var client MogileClient
  client.Hosts = addresses
  client.Timeout = time.Duration( 5 )
  err := client.setupConnection()
  if( err != nil ) {
    return nil, err
  }
  return &client, nil
}

func makeRequest( cmd string, args url.Values ) string {
  return cmd + " " + args.Encode() + "\r\n"
}

func ( c *MogileClient ) doRequest( cmd string, args url.Values, isIdempotent bool ) ( url.Values, error ) {
  request := makeRequest( cmd, args )

  c.Lock()
  defer c.Unlock()
  var data []byte = make( []byte, 512 )
  deadlineError := c.Socket.SetDeadline( time.Now().Add( time.Second * 30 ) );
  if( deadlineError != nil ) {
    err = c.setupConnection();
    if( err != nil ) {
      return nil, err
    }
    deadlineError = c.Socket.SetDeadline( time.Now().Add( time.Second * 30 ) );
    if( deadlineError != nil ) {
      return nil, deadlineError
    }
  }
  for {
    bytesSent, err := c.Socket.Write( []byte(request) )
    if( err != nil ) {
      err = c.setupConnection();
      if( err != nil ) {
        return nil, err
      }
      bytesSent, err = c.Socket.Write( []byte(request) )
      if( err != nil ) {
        return nil, errors.New( "Socket error on write after retry!" )
      }
    }
    if( err != nil || bytesSent != len( request )) {
      c.Shutdown()
      if( err == nil ) {
        return nil, SocketError
      } else {
        return nil, err
      }
    }
    c.Socket.SetReadDeadline( time.Now().Add( time.Second * 5 ) );
    bytes, readError := c.Socket.Read( data )
    data = data[0:bytes]
    if( readError != nil ) {
      return nil, readError
    }
    if( newlineRegex.Match( data ) ) {
      break;
    }
    if( bytes > 0 ) {
      return nil, errors.New( "Bad mogile server response: " + string(data) );
    }
    if( !isIdempotent ) {
      return nil, errors.New( "Unexpected EOF after: " + request );
    }
  }
  return c.parseResponse( string( data ) )
}
func (c *MogileClient ) Shutdown() {
//TODO: Write me
}

func (c *MogileClient ) parseResponse( line string ) (url.Values, error) {
  if( errRegex.MatchString( line ) ) {
    results := errRegex.FindAllStringSubmatch( line, -1 )
    c.LastError = results[0][1]
    c.LastErrorMessage = results[0][2]
    return nil, errorCode( c.LastError )
  }
  matches := successRegex.FindStringSubmatch( line )
  if ( matches != nil ) {
    if( len( matches ) > 1 ) {
      return unescapeResponse( matches[1] ), nil
    } else {
      return nil, nil
    }
  }
  return nil, InvalidResponseError
}
func errorCode( err string  ) error {
  return errors.New( "Mogile error: " + err );
}

/*
func (s Socket ) isReadable( timeout int ) bool {
  for {
    t0 := Time.now
    found = IO.select( [s], nil, nil, timeout )
    return true if found && found[0]
    timeleft -= (Time.now = t0 )
    if( timeleft < 0 ) {
      peer = s.peer_name
      Shutdown();
      raise "Unreadable socket!"
      break
    }
    Shutdown();
  }
  return false;
}
*/

func (c *MogileClient ) setupConnection() error {
  order := rand.Perm( len( c.Hosts ) )
  if( c.Socket != nil ) {
    c.Socket.Close()
  }
  for i, _ := range( order ) {
    conn, err := net.DialTCP( "tcp4", nil, c.Hosts[i] )
    if( err != nil ) {
      continue
    }

    c.Socket = conn
    conn.SetKeepAlive( true )
    return nil
  }
  return errors.New( "Connection was not setup" )
}

var escapeRegex *regexp.Regexp = regexp.MustCompile( "/%([a-f0-9][a-f0-9])/i" )
func unescapeResponse( s string ) url.Values {
    rv := url.Values{}
    dataLines := strings.Split( s, "&" )
    for _, part := range( dataLines ) {
      if len( part ) == 0 {
        continue
      }
      data := strings.SplitN( part, "=", 2 )
      rv.Add( data[0], data[1] )
    }
    return rv
}

func ( c *MogileClient ) Noop() ( error ) {
  _, err := c.doRequest( "noop", url.Values{}, true )
  return err
}
func ( c *MogileClient ) Sleep() ( error ) {
  _, err := c.doRequest( "sleep", url.Values{}, true )
  return err
}

func ( c *MogileClient ) FileDebug() ( url.Values, error ){
  return c.doRequest( "file_debug", url.Values{}, true )
}

func ( c *MogileClient ) GetHosts() ( []*MogileHost, error ){
  data, err := c.doRequest( "get_hosts", url.Values{}, true )
  numHosts, _ := strconv.Atoi( data.Get( "hosts" ) )
  rVal := make( []*MogileHost, numHosts )
  for i := 0; i < numHosts; i++ {
    base := "host" + strconv.Itoa( i + 1 ) + "_"
    var h MogileHost
    h.Name = data.Get( base + "hostname" )
    h.Status = data.Get( base + "status" )
    altMask := data.Get( base + "altmask" )
    if( len( altMask ) > 0 ) {
      h.AlternateMask = &altMask
    }
    altIp := data.Get( base + "altip" )
    if( len( altIp ) > 0 ) {
      h.AlternateIp, _ = net.ResolveIPAddr( "ip", altIp )
    }
    hostIp := data.Get( base + "hostip" )
    if( len( hostIp ) > 0 ) {
      h.IpAddress, _ = net.ResolveIPAddr( "ip", hostIp )
    }
    h.Id, _ = strconv.Atoi( data.Get( "hostid" ) )
    h.Port, _ = strconv.Atoi( data.Get( "http_port" ) )
    getPort := data.Get( "http_get_port" )
    if( len( getPort ) > 0 ) {
      p, _ := strconv.Atoi( getPort )
      h.GetPort = &p
    }
    rVal[i] = &h
  }
  return rVal, err
}
func ( c *MogileClient ) GetDevices() ( []*MogileDevice, error ){
  data, err := c.doRequest( "get_devices", url.Values{}, true )
  numDevices, _ := strconv.Atoi( data.Get( "devices" ) )
  rVal := make( []*MogileDevice, numDevices )
  for i := 0; i < numDevices; i++  {
    var d MogileDevice
    base := "dev" + strconv.Itoa( i + 1 ) + "_"
    d.mbAsof = data.Get( base + "mb_asof" )
    d.HostId, _ = strconv.Atoi( data.Get( base + "hostid" ) )
    d.SpaceUsed, _ = strconv.Atoi( data.Get( base + "mb_used" ) )
    d.SpaceFree, _ = strconv.Atoi( data.Get( base + "mb_free" ) )
    d.SpaceTotal, _ = strconv.Atoi( data.Get( base + "mb_total" ) )
    d.Id, _ = strconv.Atoi( data.Get( base + "devid" ) )
    d.Status = data.Get( base+"status" )
    d.State = data.Get( base + "observed_state" )
    d.Weight, _ = strconv.Atoi( data.Get( base + "weight" ) )
    d.Utilization = data.Get( base + "utilization" )
    md5, _ := strconv.Atoi( data.Get( base + "reject_bad_md5" ) )
    d.RejectBadMd5 =  ( md5 == 1 )
    rVal[i] = &d

  }
  return rVal, err
}
func ( c *MogileClient ) CreateDevice() ( error ) {
  _, err := c.doRequest( "create_device", url.Values{}, false )
  return err
}
func ( c *MogileClient ) CreateClass() ( error ) {
  _, err := c.doRequest( "create_class", url.Values{}, false )
  return err
}
func ( c *MogileClient ) UpdateClass() ( error ) {
  _, err := c.doRequest( "update_class", url.Values{}, false )
  return err
}
func ( c *MogileClient ) DeleteClass() ( error ) {
  _, err := c.doRequest( "delete_class", url.Values{}, false )
  return err
}
func ( c *MogileClient ) CreateHost() ( error ) {
  _, err := c.doRequest( "create_host", url.Values{}, false )
  return err
}
func ( c *MogileClient ) UpdateHost() ( error ) {
  _, err := c.doRequest( "update_host", url.Values{}, false )
  return err
}
func ( c *MogileClient ) DeleteHost() ( error ) {
  _, err := c.doRequest( "delete_host", url.Values{}, false )
  return err
}

func ( c *MogileClient ) SetState() ( error ) {
  _, err := c.doRequest( "set_state", url.Values{}, false )
  return err
}
func ( c *MogileClient ) SetWeight() ( error ) {
  _, err := c.doRequest( "set_weight", url.Values{}, false )
  return err
}
func ( c *MogileClient ) ReplicateNow() ( error ) {
  _, err := c.doRequest( "replice_now", url.Values{}, false )
  return err
}

func ( c *MogileClient ) Stats() ( *string, error ) {
  return nil, NotImplementedError
}

func ( f *MogileFid ) RandomPath() string {
  return f.Paths[ rand.Int31n( int32( len( f.Paths ) ) )  ];
}

<commit_msg>typo fix.<commit_after>package gomog

import( "net"
  "errors"
  "sync"
  "strconv"
  "strings"
  "time"
  "net/url"
  "math/rand"
  "regexp"
  )

var errRegex *regexp.Regexp = regexp.MustCompile( "^ERR\\s+(\\w+)\\s*([^\\r\\n]*)" )
var successRegex *regexp.Regexp = regexp.MustCompile( "^OK\\s+\\d*\\s*(\\S*)" )
var newlineRegex *regexp.Regexp = regexp.MustCompile( "\r?\n\\z" )

var SocketError error = errors.New( "Socket error!" )
var SocketReadError error = errors.New( "Socket read error!" )
var TruncatedRequestError error = errors.New( "Request sent was unexpectedly truncated" )
var InvalidResponseError error = errors.New( "Response was not valid!" )
var NotImplementedError error = errors.New( "That command is not implemented yet" )

type MogileHost struct {
  Name string
  AlternateMask *string
  AlternateIp *net.IPAddr
  IpAddress *net.IPAddr
  Id int
  Status string
  Port int
  GetPort *int
}
type MogileFid struct {
  DeviceId int
  Class string
  FileId int
  DevCount int
  Size int64
  Key string
  Domain string
  Paths []string
}
type MogileDevice struct {
  mbAsof string
  HostId int
  SpaceUsed int
  SpaceFree int
  SpaceTotal int
  Id int
  Status string
  State string
  Weight int
  Utilization string
  RejectBadMd5 bool
}
type MogileClient struct {
  sync.Mutex
  Hosts []*net.TCPAddr
  Timeout time.Duration
  Socket *net.TCPConn
  LastError string
  LastErrorMessage string
}
func New( hosts []string ) ( *MogileClient, error ) {
  addresses := make( []*net.TCPAddr, 0 )
  for i, _ := range( hosts ) {
    address, err := net.ResolveTCPAddr( "tcp", hosts[i] );
    if( err != nil ) {
      return nil, errors.New( "Could not read as address: " + hosts[i] );
    }
    addresses = append( addresses, address )
  }
  var client MogileClient
  client.Hosts = addresses
  client.Timeout = time.Duration( 5 )
  err := client.setupConnection()
  if( err != nil ) {
    return nil, err
  }
  return &client, nil
}

func makeRequest( cmd string, args url.Values ) string {
  return cmd + " " + args.Encode() + "\r\n"
}

func ( c *MogileClient ) doRequest( cmd string, args url.Values, isIdempotent bool ) ( url.Values, error ) {
  request := makeRequest( cmd, args )

  c.Lock()
  defer c.Unlock()
  var data []byte = make( []byte, 512 )
  deadlineError := c.Socket.SetDeadline( time.Now().Add( time.Second * 30 ) );
  if( deadlineError != nil ) {
    deadlineError = c.setupConnection();
    if( deadlineError != nil ) {
      return nil, deadlineError
    }
    deadlineError = c.Socket.SetDeadline( time.Now().Add( time.Second * 30 ) );
    if( deadlineError != nil ) {
      return nil, deadlineError
    }
  }
  for {
    bytesSent, err := c.Socket.Write( []byte(request) )
    if( err != nil ) {
      err = c.setupConnection();
      if( err != nil ) {
        return nil, err
      }
      bytesSent, err = c.Socket.Write( []byte(request) )
      if( err != nil ) {
        return nil, errors.New( "Socket error on write after retry!" )
      }
    }
    if( err != nil || bytesSent != len( request )) {
      c.Shutdown()
      if( err == nil ) {
        return nil, SocketError
      } else {
        return nil, err
      }
    }
    c.Socket.SetReadDeadline( time.Now().Add( time.Second * 5 ) );
    bytes, readError := c.Socket.Read( data )
    data = data[0:bytes]
    if( readError != nil ) {
      return nil, readError
    }
    if( newlineRegex.Match( data ) ) {
      break;
    }
    if( bytes > 0 ) {
      return nil, errors.New( "Bad mogile server response: " + string(data) );
    }
    if( !isIdempotent ) {
      return nil, errors.New( "Unexpected EOF after: " + request );
    }
  }
  return c.parseResponse( string( data ) )
}
func (c *MogileClient ) Shutdown() {
//TODO: Write me
}

func (c *MogileClient ) parseResponse( line string ) (url.Values, error) {
  if( errRegex.MatchString( line ) ) {
    results := errRegex.FindAllStringSubmatch( line, -1 )
    c.LastError = results[0][1]
    c.LastErrorMessage = results[0][2]
    return nil, errorCode( c.LastError )
  }
  matches := successRegex.FindStringSubmatch( line )
  if ( matches != nil ) {
    if( len( matches ) > 1 ) {
      return unescapeResponse( matches[1] ), nil
    } else {
      return nil, nil
    }
  }
  return nil, InvalidResponseError
}
func errorCode( err string  ) error {
  return errors.New( "Mogile error: " + err );
}

/*
func (s Socket ) isReadable( timeout int ) bool {
  for {
    t0 := Time.now
    found = IO.select( [s], nil, nil, timeout )
    return true if found && found[0]
    timeleft -= (Time.now = t0 )
    if( timeleft < 0 ) {
      peer = s.peer_name
      Shutdown();
      raise "Unreadable socket!"
      break
    }
    Shutdown();
  }
  return false;
}
*/

func (c *MogileClient ) setupConnection() error {
  order := rand.Perm( len( c.Hosts ) )
  if( c.Socket != nil ) {
    c.Socket.Close()
  }
  for i, _ := range( order ) {
    conn, err := net.DialTCP( "tcp4", nil, c.Hosts[i] )
    if( err != nil ) {
      continue
    }

    c.Socket = conn
    conn.SetKeepAlive( true )
    return nil
  }
  return errors.New( "Connection was not setup" )
}

var escapeRegex *regexp.Regexp = regexp.MustCompile( "/%([a-f0-9][a-f0-9])/i" )
func unescapeResponse( s string ) url.Values {
    rv := url.Values{}
    dataLines := strings.Split( s, "&" )
    for _, part := range( dataLines ) {
      if len( part ) == 0 {
        continue
      }
      data := strings.SplitN( part, "=", 2 )
      rv.Add( data[0], data[1] )
    }
    return rv
}

func ( c *MogileClient ) Noop() ( error ) {
  _, err := c.doRequest( "noop", url.Values{}, true )
  return err
}
func ( c *MogileClient ) Sleep() ( error ) {
  _, err := c.doRequest( "sleep", url.Values{}, true )
  return err
}

func ( c *MogileClient ) FileDebug() ( url.Values, error ){
  return c.doRequest( "file_debug", url.Values{}, true )
}

func ( c *MogileClient ) GetHosts() ( []*MogileHost, error ){
  data, err := c.doRequest( "get_hosts", url.Values{}, true )
  numHosts, _ := strconv.Atoi( data.Get( "hosts" ) )
  rVal := make( []*MogileHost, numHosts )
  for i := 0; i < numHosts; i++ {
    base := "host" + strconv.Itoa( i + 1 ) + "_"
    var h MogileHost
    h.Name = data.Get( base + "hostname" )
    h.Status = data.Get( base + "status" )
    altMask := data.Get( base + "altmask" )
    if( len( altMask ) > 0 ) {
      h.AlternateMask = &altMask
    }
    altIp := data.Get( base + "altip" )
    if( len( altIp ) > 0 ) {
      h.AlternateIp, _ = net.ResolveIPAddr( "ip", altIp )
    }
    hostIp := data.Get( base + "hostip" )
    if( len( hostIp ) > 0 ) {
      h.IpAddress, _ = net.ResolveIPAddr( "ip", hostIp )
    }
    h.Id, _ = strconv.Atoi( data.Get( "hostid" ) )
    h.Port, _ = strconv.Atoi( data.Get( "http_port" ) )
    getPort := data.Get( "http_get_port" )
    if( len( getPort ) > 0 ) {
      p, _ := strconv.Atoi( getPort )
      h.GetPort = &p
    }
    rVal[i] = &h
  }
  return rVal, err
}
func ( c *MogileClient ) GetDevices() ( []*MogileDevice, error ){
  data, err := c.doRequest( "get_devices", url.Values{}, true )
  numDevices, _ := strconv.Atoi( data.Get( "devices" ) )
  rVal := make( []*MogileDevice, numDevices )
  for i := 0; i < numDevices; i++  {
    var d MogileDevice
    base := "dev" + strconv.Itoa( i + 1 ) + "_"
    d.mbAsof = data.Get( base + "mb_asof" )
    d.HostId, _ = strconv.Atoi( data.Get( base + "hostid" ) )
    d.SpaceUsed, _ = strconv.Atoi( data.Get( base + "mb_used" ) )
    d.SpaceFree, _ = strconv.Atoi( data.Get( base + "mb_free" ) )
    d.SpaceTotal, _ = strconv.Atoi( data.Get( base + "mb_total" ) )
    d.Id, _ = strconv.Atoi( data.Get( base + "devid" ) )
    d.Status = data.Get( base+"status" )
    d.State = data.Get( base + "observed_state" )
    d.Weight, _ = strconv.Atoi( data.Get( base + "weight" ) )
    d.Utilization = data.Get( base + "utilization" )
    md5, _ := strconv.Atoi( data.Get( base + "reject_bad_md5" ) )
    d.RejectBadMd5 =  ( md5 == 1 )
    rVal[i] = &d

  }
  return rVal, err
}
func ( c *MogileClient ) CreateDevice() ( error ) {
  _, err := c.doRequest( "create_device", url.Values{}, false )
  return err
}
func ( c *MogileClient ) CreateClass() ( error ) {
  _, err := c.doRequest( "create_class", url.Values{}, false )
  return err
}
func ( c *MogileClient ) UpdateClass() ( error ) {
  _, err := c.doRequest( "update_class", url.Values{}, false )
  return err
}
func ( c *MogileClient ) DeleteClass() ( error ) {
  _, err := c.doRequest( "delete_class", url.Values{}, false )
  return err
}
func ( c *MogileClient ) CreateHost() ( error ) {
  _, err := c.doRequest( "create_host", url.Values{}, false )
  return err
}
func ( c *MogileClient ) UpdateHost() ( error ) {
  _, err := c.doRequest( "update_host", url.Values{}, false )
  return err
}
func ( c *MogileClient ) DeleteHost() ( error ) {
  _, err := c.doRequest( "delete_host", url.Values{}, false )
  return err
}

func ( c *MogileClient ) SetState() ( error ) {
  _, err := c.doRequest( "set_state", url.Values{}, false )
  return err
}
func ( c *MogileClient ) SetWeight() ( error ) {
  _, err := c.doRequest( "set_weight", url.Values{}, false )
  return err
}
func ( c *MogileClient ) ReplicateNow() ( error ) {
  _, err := c.doRequest( "replice_now", url.Values{}, false )
  return err
}

func ( c *MogileClient ) Stats() ( *string, error ) {
  return nil, NotImplementedError
}

func ( f *MogileFid ) RandomPath() string {
  return f.Paths[ rand.Int31n( int32( len( f.Paths ) ) )  ];
}

<|endoftext|>


<commit_before>package mackerel

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
)

/*
{
  "monitors": [
    {
      "id": "2cSZzK3XfmG",
      "type": "connectivity",
      "isMute": false,
      "scopes": [],
      "excludeScopes": []
    },
    {
      "id"  : "2cSZzK3XfmG",
      "type": "host",
      "isMute": false,
      "name": "disk.aa-00.writes.delta",
      "duration": 3,
      "metric": "disk.aa-00.writes.delta",
      "operator": ">",
      "warning": 20000.0,
      "critical": 400000.0,
      "scopes": [
        "SomeService"
      ],
      "excludeScopes": [
        "SomeService: db-slave-backup"
      ],
      "notificationInterval": 60
    },
    {
      "id"  : "2cSZzK3XfmG",
      "type": "service",
      "isMute": false,
      "name": "SomeService - custom.access_num.4xx_count",
      "service": "SomeService",
      "duration": 1,
      "metric": "custom.access_num.4xx_count",
      "operator": ">",
      "warning": 50.0,
      "critical": 100.0
    },
    {
      "id"  : "2cSZzK3XfmG",
      "type": "external",
      "isMute": false,
      "name": "example.com",
      "method": "GET",
      "url": "http://www.example.com",
      "service": "SomeService",
      "maxCheckAttempts": 1,
      "responseTimeCritical": 10000,
      "responseTimeWarning": 5000,
      "responseTimeDuration": 5,
      "certificationExpirationCritical": 15,
      "certificationExpirationWarning": 30,
      "requestBody": "Request Body",
      "containsString": "Example",
      "skipCertificateVerification": true,
      "headers": [
        { "name": "Cache-Control", "value": "no-cache"}
      ]
    }
  ]
}
*/

// Monitor represents interface to which each monitor type must confirm to.
type Monitor interface {
	MonitorType() string
	MonitorID() string
	MonitorName() string

	isMonitor()
}

const (
	monitorTypeConnectivity  = "connectivity"
	monitorTypeHostMeric     = "host"
	monitorTypeServiceMetric = "service"
	monitorTypeExternalHTTP  = "external"
	monitorTypeExpression    = "expression"
)

// Ensure each monitor type conforms to the Monitor interface.
var (
	_ Monitor = (*MonitorConnectivity)(nil)
	_ Monitor = (*MonitorHostMetric)(nil)
	_ Monitor = (*MonitorServiceMetric)(nil)
	_ Monitor = (*MonitorExternalHTTP)(nil)
	_ Monitor = (*MonitorExpression)(nil)
)

// Ensure only monitor types defined in this package can be assigned to the
// Monitor interface.
//
func (m *MonitorConnectivity) isMonitor()  {}
func (m *MonitorHostMetric) isMonitor()    {}
func (m *MonitorServiceMetric) isMonitor() {}
func (m *MonitorExternalHTTP) isMonitor()  {}
func (m *MonitorExpression) isMonitor()    {}

// MonitorConnectivity represents connectivity monitor.
type MonitorConnectivity struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Scopes        []string `json:"scopes,omitempty"`
	ExcludeScopes []string `json:"excludeScopes,omitempty"`
}

// MonitorType returns monitor type.
func (m *MonitorConnectivity) MonitorType() string { return monitorTypeConnectivity }

// MonitorName returns monitor name.
func (m *MonitorConnectivity) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorConnectivity) MonitorID() string { return m.ID }

// MonitorHostMetric represents host metric monitor.
type MonitorHostMetric struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Metric   string  `json:"metric,omitempty"`
	Operator string  `json:"operator,omitempty"`
	Warning  float64 `json:"warning"`
	Critical float64 `json:"critical"`
	Duration uint64  `json:"duration,omitempty"`

	Scopes        []string `json:"scopes,omitempty"`
	ExcludeScopes []string `json:"excludeScopes,omitempty"`
}

// MonitorType returns monitor type.
func (m *MonitorHostMetric) MonitorType() string { return monitorTypeHostMeric }

// MonitorName returns monitor name.
func (m *MonitorHostMetric) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorHostMetric) MonitorID() string { return m.ID }

// MonitorServiceMetric represents service metric monitor.
type MonitorServiceMetric struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Service  string  `json:"service,omitempty"`
	Metric   string  `json:"metric,omitempty"`
	Operator string  `json:"operator,omitempty"`
	Warning  float64 `json:"warning"`
	Critical float64 `json:"critical"`
	Duration uint64  `json:"duration,omitempty"`
}

// MonitorType returns monitor type.
func (m *MonitorServiceMetric) MonitorType() string { return monitorTypeServiceMetric }

// MonitorName returns monitor name.
func (m *MonitorServiceMetric) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorServiceMetric) MonitorID() string { return m.ID }

// MonitorExternalHTTP represents external HTTP monitor.
type MonitorExternalHTTP struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Method                          string  `json:"method,omitempty"`
	URL                             string  `json:"url,omitempty"`
	MaxCheckAttempts                float64 `json:"maxCheckAttempts,omitempty"`
	Service                         string  `json:"service,omitempty"`
	ResponseTimeCritical            float64 `json:"responseTimeCritical,omitempty"`
	ResponseTimeWarning             float64 `json:"responseTimeWarning,omitempty"`
	ResponseTimeDuration            float64 `json:"responseTimeDuration,omitempty"`
	RequestBody                     string  `json:"requestBody,omitempty"`
	ContainsString                  string  `json:"containsString,omitempty"`
	CertificationExpirationCritical uint64  `json:"certificationExpirationCritical,omitempty"`
	CertificationExpirationWarning  uint64  `json:"certificationExpirationWarning,omitempty"`
	SkipCertificateVerification     bool    `json:"skipCertificateVerification,omitempty"`
	// Empty list of headers and nil are different. You have to specify empty
	// list as headers explicitly if you want to remove all headers instead of
	// using nil.
	Headers []HeaderField `json:"headers"`
}

// HeaderField represents key-value pairs in an HTTP header for external http
// monitoring.
type HeaderField struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

// MonitorType returns monitor type.
func (m *MonitorExternalHTTP) MonitorType() string { return monitorTypeExternalHTTP }

// MonitorName returns monitor name.
func (m *MonitorExternalHTTP) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorExternalHTTP) MonitorID() string { return m.ID }

// MonitorExpression represents expression monitor.
type MonitorExpression struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Expression string  `json:"expression,omitempty"`
	Operator   string  `json:"operator,omitempty"`
	Warning    float64 `json:"warning"`
	Critical   float64 `json:"critical"`
}

// MonitorType returns monitor type.
func (m *MonitorExpression) MonitorType() string { return monitorTypeExpression }

// MonitorName returns monitor name.
func (m *MonitorExpression) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorExpression) MonitorID() string { return m.ID }

// FindMonitors find monitors
func (c *Client) FindMonitors() ([]Monitor, error) {
	req, err := http.NewRequest("GET", c.urlFor("/api/v0/monitors").String(), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.Request(req)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}

	var data struct {
		Monitors []json.RawMessage `json:"monitors"`
	}
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		return nil, err
	}
	ms := make([]Monitor, 0, len(data.Monitors))
	for _, rawmes := range data.Monitors {
		m, err := decodeMonitor(rawmes)
		if err != nil {
			return nil, err
		}
		ms = append(ms, m)
	}
	return ms, err
}

// CreateMonitor creating monitor
func (c *Client) CreateMonitor(param Monitor) (Monitor, error) {
	resp, err := c.PostJSON("/api/v0/monitors", param)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}
	return decodeMonitorReader(resp.Body)
}

// UpdateMonitor update monitor
func (c *Client) UpdateMonitor(monitorID string, param Monitor) (Monitor, error) {
	resp, err := c.PutJSON(fmt.Sprintf("/api/v0/monitors/%s", monitorID), param)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}
	return decodeMonitorReader(resp.Body)
}

// DeleteMonitor update monitor
func (c *Client) DeleteMonitor(monitorID string) (Monitor, error) {
	req, err := http.NewRequest(
		"DELETE",
		c.urlFor(fmt.Sprintf("/api/v0/monitors/%s", monitorID)).String(),
		nil,
	)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Content-Type", "application/json")

	resp, err := c.Request(req)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}
	return decodeMonitorReader(resp.Body)
}

// decodeMonitor decodes json.RawMessage and returns monitor.
func decodeMonitor(mes json.RawMessage) (Monitor, error) {
	var typeData struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal(mes, &typeData); err != nil {
		return nil, err
	}
	var m Monitor
	switch typeData.Type {
	case monitorTypeConnectivity:
		m = &MonitorConnectivity{}
	case monitorTypeHostMeric:
		m = &MonitorHostMetric{}
	case monitorTypeServiceMetric:
		m = &MonitorServiceMetric{}
	case monitorTypeExternalHTTP:
		m = &MonitorExternalHTTP{}
	case monitorTypeExpression:
		m = &MonitorExpression{}
	}
	if err := json.Unmarshal(mes, m); err != nil {
		return nil, err
	}
	return m, nil
}

func decodeMonitorReader(r io.Reader) (Monitor, error) {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}
	return decodeMonitor(b)
}
<commit_msg>add support of maxCheckAttempts for host metric and service metric monitors<commit_after>package mackerel

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
)

/*
{
  "monitors": [
    {
      "id": "2cSZzK3XfmG",
      "type": "connectivity",
      "isMute": false,
      "scopes": [],
      "excludeScopes": []
    },
    {
      "id"  : "2cSZzK3XfmG",
      "type": "host",
      "isMute": false,
      "name": "disk.aa-00.writes.delta",
      "duration": 3,
      "metric": "disk.aa-00.writes.delta",
      "operator": ">",
      "warning": 20000.0,
      "critical": 400000.0,
      "scopes": [
        "SomeService"
      ],
      "excludeScopes": [
        "SomeService: db-slave-backup"
      ],
      "notificationInterval": 60
    },
    {
      "id"  : "2cSZzK3XfmG",
      "type": "service",
      "isMute": false,
      "name": "SomeService - custom.access_num.4xx_count",
      "service": "SomeService",
      "duration": 1,
      "metric": "custom.access_num.4xx_count",
      "operator": ">",
      "warning": 50.0,
      "critical": 100.0
    },
    {
      "id"  : "2cSZzK3XfmG",
      "type": "external",
      "isMute": false,
      "name": "example.com",
      "method": "GET",
      "url": "http://www.example.com",
      "service": "SomeService",
      "maxCheckAttempts": 1,
      "responseTimeCritical": 10000,
      "responseTimeWarning": 5000,
      "responseTimeDuration": 5,
      "certificationExpirationCritical": 15,
      "certificationExpirationWarning": 30,
      "requestBody": "Request Body",
      "containsString": "Example",
      "skipCertificateVerification": true,
      "headers": [
        { "name": "Cache-Control", "value": "no-cache"}
      ]
    }
  ]
}
*/

// Monitor represents interface to which each monitor type must confirm to.
type Monitor interface {
	MonitorType() string
	MonitorID() string
	MonitorName() string

	isMonitor()
}

const (
	monitorTypeConnectivity  = "connectivity"
	monitorTypeHostMeric     = "host"
	monitorTypeServiceMetric = "service"
	monitorTypeExternalHTTP  = "external"
	monitorTypeExpression    = "expression"
)

// Ensure each monitor type conforms to the Monitor interface.
var (
	_ Monitor = (*MonitorConnectivity)(nil)
	_ Monitor = (*MonitorHostMetric)(nil)
	_ Monitor = (*MonitorServiceMetric)(nil)
	_ Monitor = (*MonitorExternalHTTP)(nil)
	_ Monitor = (*MonitorExpression)(nil)
)

// Ensure only monitor types defined in this package can be assigned to the
// Monitor interface.
//
func (m *MonitorConnectivity) isMonitor()  {}
func (m *MonitorHostMetric) isMonitor()    {}
func (m *MonitorServiceMetric) isMonitor() {}
func (m *MonitorExternalHTTP) isMonitor()  {}
func (m *MonitorExpression) isMonitor()    {}

// MonitorConnectivity represents connectivity monitor.
type MonitorConnectivity struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Scopes        []string `json:"scopes,omitempty"`
	ExcludeScopes []string `json:"excludeScopes,omitempty"`
}

// MonitorType returns monitor type.
func (m *MonitorConnectivity) MonitorType() string { return monitorTypeConnectivity }

// MonitorName returns monitor name.
func (m *MonitorConnectivity) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorConnectivity) MonitorID() string { return m.ID }

// MonitorHostMetric represents host metric monitor.
type MonitorHostMetric struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Metric           string  `json:"metric,omitempty"`
	Operator         string  `json:"operator,omitempty"`
	Warning          float64 `json:"warning"`
	Critical         float64 `json:"critical"`
	Duration         uint64  `json:"duration,omitempty"`
	MaxCheckAttempts uint64  `json:"maxCheckAttempts,omitempty"`

	Scopes        []string `json:"scopes,omitempty"`
	ExcludeScopes []string `json:"excludeScopes,omitempty"`
}

// MonitorType returns monitor type.
func (m *MonitorHostMetric) MonitorType() string { return monitorTypeHostMeric }

// MonitorName returns monitor name.
func (m *MonitorHostMetric) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorHostMetric) MonitorID() string { return m.ID }

// MonitorServiceMetric represents service metric monitor.
type MonitorServiceMetric struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Service          string  `json:"service,omitempty"`
	Metric           string  `json:"metric,omitempty"`
	Operator         string  `json:"operator,omitempty"`
	Warning          float64 `json:"warning"`
	Critical         float64 `json:"critical"`
	Duration         uint64  `json:"duration,omitempty"`
	MaxCheckAttempts uint64  `json:"maxCheckAttempts,omitempty"`
}

// MonitorType returns monitor type.
func (m *MonitorServiceMetric) MonitorType() string { return monitorTypeServiceMetric }

// MonitorName returns monitor name.
func (m *MonitorServiceMetric) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorServiceMetric) MonitorID() string { return m.ID }

// MonitorExternalHTTP represents external HTTP monitor.
type MonitorExternalHTTP struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Method                          string  `json:"method,omitempty"`
	URL                             string  `json:"url,omitempty"`
	MaxCheckAttempts                uint64  `json:"maxCheckAttempts,omitempty"`
	Service                         string  `json:"service,omitempty"`
	ResponseTimeCritical            float64 `json:"responseTimeCritical,omitempty"`
	ResponseTimeWarning             float64 `json:"responseTimeWarning,omitempty"`
	ResponseTimeDuration            float64 `json:"responseTimeDuration,omitempty"`
	RequestBody                     string  `json:"requestBody,omitempty"`
	ContainsString                  string  `json:"containsString,omitempty"`
	CertificationExpirationCritical uint64  `json:"certificationExpirationCritical,omitempty"`
	CertificationExpirationWarning  uint64  `json:"certificationExpirationWarning,omitempty"`
	SkipCertificateVerification     bool    `json:"skipCertificateVerification,omitempty"`
	// Empty list of headers and nil are different. You have to specify empty
	// list as headers explicitly if you want to remove all headers instead of
	// using nil.
	Headers []HeaderField `json:"headers"`
}

// HeaderField represents key-value pairs in an HTTP header for external http
// monitoring.
type HeaderField struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

// MonitorType returns monitor type.
func (m *MonitorExternalHTTP) MonitorType() string { return monitorTypeExternalHTTP }

// MonitorName returns monitor name.
func (m *MonitorExternalHTTP) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorExternalHTTP) MonitorID() string { return m.ID }

// MonitorExpression represents expression monitor.
type MonitorExpression struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Expression string  `json:"expression,omitempty"`
	Operator   string  `json:"operator,omitempty"`
	Warning    float64 `json:"warning"`
	Critical   float64 `json:"critical"`
}

// MonitorType returns monitor type.
func (m *MonitorExpression) MonitorType() string { return monitorTypeExpression }

// MonitorName returns monitor name.
func (m *MonitorExpression) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorExpression) MonitorID() string { return m.ID }

// FindMonitors find monitors
func (c *Client) FindMonitors() ([]Monitor, error) {
	req, err := http.NewRequest("GET", c.urlFor("/api/v0/monitors").String(), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.Request(req)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}

	var data struct {
		Monitors []json.RawMessage `json:"monitors"`
	}
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		return nil, err
	}
	ms := make([]Monitor, 0, len(data.Monitors))
	for _, rawmes := range data.Monitors {
		m, err := decodeMonitor(rawmes)
		if err != nil {
			return nil, err
		}
		ms = append(ms, m)
	}
	return ms, err
}

// CreateMonitor creating monitor
func (c *Client) CreateMonitor(param Monitor) (Monitor, error) {
	resp, err := c.PostJSON("/api/v0/monitors", param)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}
	return decodeMonitorReader(resp.Body)
}

// UpdateMonitor update monitor
func (c *Client) UpdateMonitor(monitorID string, param Monitor) (Monitor, error) {
	resp, err := c.PutJSON(fmt.Sprintf("/api/v0/monitors/%s", monitorID), param)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}
	return decodeMonitorReader(resp.Body)
}

// DeleteMonitor update monitor
func (c *Client) DeleteMonitor(monitorID string) (Monitor, error) {
	req, err := http.NewRequest(
		"DELETE",
		c.urlFor(fmt.Sprintf("/api/v0/monitors/%s", monitorID)).String(),
		nil,
	)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Content-Type", "application/json")

	resp, err := c.Request(req)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}
	return decodeMonitorReader(resp.Body)
}

// decodeMonitor decodes json.RawMessage and returns monitor.
func decodeMonitor(mes json.RawMessage) (Monitor, error) {
	var typeData struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal(mes, &typeData); err != nil {
		return nil, err
	}
	var m Monitor
	switch typeData.Type {
	case monitorTypeConnectivity:
		m = &MonitorConnectivity{}
	case monitorTypeHostMeric:
		m = &MonitorHostMetric{}
	case monitorTypeServiceMetric:
		m = &MonitorServiceMetric{}
	case monitorTypeExternalHTTP:
		m = &MonitorExternalHTTP{}
	case monitorTypeExpression:
		m = &MonitorExpression{}
	}
	if err := json.Unmarshal(mes, m); err != nil {
		return nil, err
	}
	return m, nil
}

func decodeMonitorReader(r io.Reader) (Monitor, error) {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}
	return decodeMonitor(b)
}
<|endoftext|>


<commit_before>package testflight_test

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
	"testing"
	"time"

	"github.com/cloudfoundry-incubator/garden/warden"
	WardenRunner "github.com/cloudfoundry-incubator/warden-linux/integration/runner"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gexec"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"
	"github.com/tedsuo/ifrit/grouper"
)

var externalAddr string

var processes ifrit.Process
var wardenClient warden.Client

var fixturesDir = "./fixtures"
var atcDir string

var builtComponents map[string]string

var wardenBinPath string

var helperRootfs string

var _ = SynchronizedBeforeSuite(func() []byte {
	wardenBinPath = os.Getenv("WARDEN_BINPATH")
	Ω(wardenBinPath).ShouldNot(BeEmpty(), "must provide $WARDEN_BINPATH")

	Ω(os.Getenv("BASE_GOPATH")).ShouldNot(BeEmpty(), "must provide $BASE_GOPATH")

	turbineBin, err := buildWithGodeps("github.com/concourse/turbine", "-race")
	Ω(err).ShouldNot(HaveOccurred())

	atcBin, err := buildWithGodeps("github.com/concourse/atc", "-race")
	Ω(err).ShouldNot(HaveOccurred())

	gliderBin, err := buildWithGodeps("github.com/concourse/glider", "-race")
	Ω(err).ShouldNot(HaveOccurred())

	flyBin, err := buildWithGodeps("github.com/concourse/fly", "-race")
	Ω(err).ShouldNot(HaveOccurred())

	wardenLinuxBin, err := buildWithGodeps("github.com/cloudfoundry-incubator/warden-linux", "-race")
	Ω(err).ShouldNot(HaveOccurred())

	components, err := json.Marshal(map[string]string{
		"turbine":      turbineBin,
		"atc":          atcBin,
		"glider":       gliderBin,
		"fly":          flyBin,
		"warden-linux": wardenLinuxBin,
	})
	Ω(err).ShouldNot(HaveOccurred())

	return components
}, func(components []byte) {
	err := json.Unmarshal(components, &builtComponents)
	Ω(err).ShouldNot(HaveOccurred())

	atcDir = findSource("github.com/concourse/atc")
})

var _ = BeforeEach(func() {
	externalAddr = os.Getenv("EXTERNAL_ADDRESS")
	Ω(externalAddr).ShouldNot(BeEmpty(), "must specify $EXTERNAL_ADDRESS")

	archiveResourceRootfs := os.Getenv("ARCHIVE_RESOURCE_ROOTFS")
	Ω(archiveResourceRootfs).ShouldNot(BeEmpty(), "must specify $ARCHIVE_RESOURCE_ROOTFS")

	gitResourceRootfs := os.Getenv("GIT_RESOURCE_ROOTFS")
	Ω(gitResourceRootfs).ShouldNot(BeEmpty(), "must specify $GIT_RESOURCE_ROOTFS")

	helperRootfs = os.Getenv("HELPER_ROOTFS")
	Ω(helperRootfs).ShouldNot(BeEmpty(), "must specify $HELPER_ROOTFS")

	wardenAddr := fmt.Sprintf("127.0.0.1:%d", 4859+GinkgoParallelNode())

	wardenRunner := WardenRunner.New(
		"tcp",
		wardenAddr,
		builtComponents["warden-linux"],
		wardenBinPath,
		"bogus/rootfs",
		"/tmp",
	)

	wardenClient = wardenRunner.NewClient()

	turbineRunner := &ginkgomon.Runner{
		Name:          "turbine",
		AnsiColorCode: "33m",
		Command: exec.Command(
			builtComponents["turbine"],
			"-wardenNetwork", "tcp",
			"-wardenAddr", wardenAddr,
			"-resourceTypes", fmt.Sprintf(`{
				"archive": "%s",
				"git": "%s"
			}`, archiveResourceRootfs, gitResourceRootfs),
		),
		StartCheck:        "listening",
		StartCheckTimeout: 30 * time.Second,
	}

	gliderRunner := &ginkgomon.Runner{
		Name:          "glider",
		AnsiColorCode: "32m",
		Command: exec.Command(
			builtComponents["glider"],
			"-peerAddr", externalAddr+":5637",
		),
		StartCheck: "listening",
	}

	processes = grouper.EnvokeGroup(grouper.RunGroup{
		"turbine":      turbineRunner,
		"glider":       gliderRunner,
		"warden-linux": wardenRunner,
	})

	Consistently(processes.Wait(), 1*time.Second).ShouldNot(Receive())

	os.Setenv("GLIDER_URL", "http://127.0.0.1:5637")
})

var _ = AfterEach(func() {
	processes.Signal(syscall.SIGINT)

	select {
	case <-process.Wait():
	case <-time.After(10 * time.Second):
		println("!!!!!!!!!!!!!!!!!!!!!!!!!!!! EXIT TIMEOUT")

		processes.Signal(syscall.SIGQUIT)
		Eventually(processes.Wait(), 10*time.Second).Should(Receive())

		Fail("processes did not exit within 10s; SIGQUIT sent")
	}
})

func TestTestFlight(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "TestFlight Suite")
}

func findSource(pkg string) string {
	for _, path := range filepath.SplitList(os.Getenv("BASE_GOPATH")) {
		srcPath := filepath.Join(path, "src", pkg)

		_, err := os.Stat(srcPath)
		if err != nil {
			continue
		}

		return srcPath
	}

	return ""
}

func buildWithGodeps(pkg string, args ...string) (string, error) {
	srcPath := findSource(pkg)
	Ω(srcPath).ShouldNot(BeEmpty(), "could not find source for "+pkg)

	gopath := fmt.Sprintf(
		"%s%c%s",
		filepath.Join(srcPath, "Godeps", "_workspace"),
		os.PathListSeparator,
		os.Getenv("BASE_GOPATH"),
	)

	return gexec.BuildIn(gopath, pkg, args...)
}
<commit_msg>fix typo<commit_after>package testflight_test

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
	"testing"
	"time"

	"github.com/cloudfoundry-incubator/garden/warden"
	WardenRunner "github.com/cloudfoundry-incubator/warden-linux/integration/runner"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gexec"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"
	"github.com/tedsuo/ifrit/grouper"
)

var externalAddr string

var processes ifrit.Process
var wardenClient warden.Client

var fixturesDir = "./fixtures"
var atcDir string

var builtComponents map[string]string

var wardenBinPath string

var helperRootfs string

var _ = SynchronizedBeforeSuite(func() []byte {
	wardenBinPath = os.Getenv("WARDEN_BINPATH")
	Ω(wardenBinPath).ShouldNot(BeEmpty(), "must provide $WARDEN_BINPATH")

	Ω(os.Getenv("BASE_GOPATH")).ShouldNot(BeEmpty(), "must provide $BASE_GOPATH")

	turbineBin, err := buildWithGodeps("github.com/concourse/turbine", "-race")
	Ω(err).ShouldNot(HaveOccurred())

	atcBin, err := buildWithGodeps("github.com/concourse/atc", "-race")
	Ω(err).ShouldNot(HaveOccurred())

	gliderBin, err := buildWithGodeps("github.com/concourse/glider", "-race")
	Ω(err).ShouldNot(HaveOccurred())

	flyBin, err := buildWithGodeps("github.com/concourse/fly", "-race")
	Ω(err).ShouldNot(HaveOccurred())

	wardenLinuxBin, err := buildWithGodeps("github.com/cloudfoundry-incubator/warden-linux", "-race")
	Ω(err).ShouldNot(HaveOccurred())

	components, err := json.Marshal(map[string]string{
		"turbine":      turbineBin,
		"atc":          atcBin,
		"glider":       gliderBin,
		"fly":          flyBin,
		"warden-linux": wardenLinuxBin,
	})
	Ω(err).ShouldNot(HaveOccurred())

	return components
}, func(components []byte) {
	err := json.Unmarshal(components, &builtComponents)
	Ω(err).ShouldNot(HaveOccurred())

	atcDir = findSource("github.com/concourse/atc")
})

var _ = BeforeEach(func() {
	externalAddr = os.Getenv("EXTERNAL_ADDRESS")
	Ω(externalAddr).ShouldNot(BeEmpty(), "must specify $EXTERNAL_ADDRESS")

	archiveResourceRootfs := os.Getenv("ARCHIVE_RESOURCE_ROOTFS")
	Ω(archiveResourceRootfs).ShouldNot(BeEmpty(), "must specify $ARCHIVE_RESOURCE_ROOTFS")

	gitResourceRootfs := os.Getenv("GIT_RESOURCE_ROOTFS")
	Ω(gitResourceRootfs).ShouldNot(BeEmpty(), "must specify $GIT_RESOURCE_ROOTFS")

	helperRootfs = os.Getenv("HELPER_ROOTFS")
	Ω(helperRootfs).ShouldNot(BeEmpty(), "must specify $HELPER_ROOTFS")

	wardenAddr := fmt.Sprintf("127.0.0.1:%d", 4859+GinkgoParallelNode())

	wardenRunner := WardenRunner.New(
		"tcp",
		wardenAddr,
		builtComponents["warden-linux"],
		wardenBinPath,
		"bogus/rootfs",
		"/tmp",
	)

	wardenClient = wardenRunner.NewClient()

	turbineRunner := &ginkgomon.Runner{
		Name:          "turbine",
		AnsiColorCode: "33m",
		Command: exec.Command(
			builtComponents["turbine"],
			"-wardenNetwork", "tcp",
			"-wardenAddr", wardenAddr,
			"-resourceTypes", fmt.Sprintf(`{
				"archive": "%s",
				"git": "%s"
			}`, archiveResourceRootfs, gitResourceRootfs),
		),
		StartCheck:        "listening",
		StartCheckTimeout: 30 * time.Second,
	}

	gliderRunner := &ginkgomon.Runner{
		Name:          "glider",
		AnsiColorCode: "32m",
		Command: exec.Command(
			builtComponents["glider"],
			"-peerAddr", externalAddr+":5637",
		),
		StartCheck: "listening",
	}

	processes = grouper.EnvokeGroup(grouper.RunGroup{
		"turbine":      turbineRunner,
		"glider":       gliderRunner,
		"warden-linux": wardenRunner,
	})

	Consistently(processes.Wait(), 1*time.Second).ShouldNot(Receive())

	os.Setenv("GLIDER_URL", "http://127.0.0.1:5637")
})

var _ = AfterEach(func() {
	processes.Signal(syscall.SIGINT)

	select {
	case <-processes.Wait():
	case <-time.After(10 * time.Second):
		println("!!!!!!!!!!!!!!!!!!!!!!!!!!!! EXIT TIMEOUT")

		processes.Signal(syscall.SIGQUIT)
		Eventually(processes.Wait(), 10*time.Second).Should(Receive())

		Fail("processes did not exit within 10s; SIGQUIT sent")
	}
})

func TestTestFlight(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "TestFlight Suite")
}

func findSource(pkg string) string {
	for _, path := range filepath.SplitList(os.Getenv("BASE_GOPATH")) {
		srcPath := filepath.Join(path, "src", pkg)

		_, err := os.Stat(srcPath)
		if err != nil {
			continue
		}

		return srcPath
	}

	return ""
}

func buildWithGodeps(pkg string, args ...string) (string, error) {
	srcPath := findSource(pkg)
	Ω(srcPath).ShouldNot(BeEmpty(), "could not find source for "+pkg)

	gopath := fmt.Sprintf(
		"%s%c%s",
		filepath.Join(srcPath, "Godeps", "_workspace"),
		os.PathListSeparator,
		os.Getenv("BASE_GOPATH"),
	)

	return gexec.BuildIn(gopath, pkg, args...)
}
<|endoftext|>


<commit_before>package testing

import (
	"bytes"
	"context"
	"fmt"
	"sort"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/influxdata/platform"
)

var labelCmpOptions = cmp.Options{
	cmp.Comparer(func(x, y []byte) bool {
		return bytes.Equal(x, y)
	}),
	cmp.Transformer("Sort", func(in []*platform.Label) []*platform.Label {
		out := append([]*platform.Label(nil), in...) // Copy input to avoid mutating it
		fmt.Printf("out: %v\n", out)
		sort.Slice(out, func(i, j int) bool {
			if out[i].Name != out[j].Name {
				return out[i].Name < out[j].Name
			}
			return out[i].ResourceID.String() < out[j].ResourceID.String()
		})
		fmt.Printf("sorted out: %v\n", out)
		return out
	}),
}

type LabelFields struct {
	Labels []*platform.Label
}

type labelServiceF func(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
)

// LabelService tests all the service functions.
func LabelService(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	tests := []struct {
		name string
		fn   labelServiceF
	}{
		{
			name: "CreateLabel",
			fn:   CreateLabel,
		},
		{
			name: "FindLabels",
			fn:   FindLabels,
		},
		{
			name: "DeleteLabel",
			fn:   DeleteLabel,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.fn(init, t)
		})
	}
}

func CreateLabel(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		label *platform.Label
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "basic create label",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: &platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag2",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "duplicate labels fail",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: &platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
				err: fmt.Errorf("label Tag1 already exists"),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.Background()
			err := s.CreateLabel(ctx, tt.args.label)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}
			defer s.DeleteLabel(ctx, *tt.args.label)

			labels, err := s.FindLabels(ctx, platform.LabelFilter{})
			if err != nil {
				t.Fatalf("failed to retrieve labels: %v", err)
			}
			if diff := cmp.Diff(labels, tt.wants.labels, labelCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}

func FindLabels(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		filter platform.LabelFilter
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "basic find labels",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
			args: args{
				filter: platform.LabelFilter{},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "find labels filtering",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
					{
						ResourceID: MustIDBase16(bucketTwoID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				filter: platform.LabelFilter{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.TODO()
			labels, err := s.FindLabels(ctx, tt.args.filter)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}

			if diff := cmp.Diff(labels, tt.wants.labels, labelCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}

func DeleteLabel(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		label platform.Label
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "basic delete label",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
			args: args{
				label: platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "deleting a non-existant label",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag2",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
				err: platform.ErrLabelNotFound,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.TODO()
			err := s.DeleteLabel(ctx, tt.args.label)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}

			labels, err := s.FindLabels(ctx, platform.LabelFilter{})
			if err != nil {
				t.Fatalf("failed to retrieve labels: %v", err)
			}
			if diff := cmp.Diff(labels, tt.wants.labels, labelCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}
<commit_msg>add failing tests<commit_after>package testing

import (
	"bytes"
	"context"
	"fmt"
	"sort"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/influxdata/platform"
)

var (
	validColor   = "fff000"
	invalidColor = "xyz123"
)

var labelCmpOptions = cmp.Options{
	cmp.Comparer(func(x, y []byte) bool {
		return bytes.Equal(x, y)
	}),
	cmp.Transformer("Sort", func(in []*platform.Label) []*platform.Label {
		out := append([]*platform.Label(nil), in...) // Copy input to avoid mutating it
		sort.Slice(out, func(i, j int) bool {
			if out[i].Name != out[j].Name {
				return out[i].Name < out[j].Name
			}
			return out[i].ResourceID.String() < out[j].ResourceID.String()
		})
		return out
	}),
}

type LabelFields struct {
	Labels []*platform.Label
}

type labelServiceF func(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
)

// LabelService tests all the service functions.
func LabelService(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	tests := []struct {
		name string
		fn   labelServiceF
	}{
		{
			name: "CreateLabel",
			fn:   CreateLabel,
		},
		{
			name: "FindLabels",
			fn:   FindLabels,
		},
		{
			name: "UpdateLabel",
			fn:   UpdateLabel,
		},
		{
			name: "DeleteLabel",
			fn:   DeleteLabel,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.fn(init, t)
		})
	}
}

func CreateLabel(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		label *platform.Label
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "basic create label",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: &platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag2",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "duplicate labels fail",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: &platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
				err: fmt.Errorf("label Tag1 already exists"),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.Background()
			err := s.CreateLabel(ctx, tt.args.label)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}
			defer s.DeleteLabel(ctx, *tt.args.label)

			labels, err := s.FindLabels(ctx, platform.LabelFilter{})
			if err != nil {
				t.Fatalf("failed to retrieve labels: %v", err)
			}
			if diff := cmp.Diff(labels, tt.wants.labels, labelCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}

func FindLabels(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		filter platform.LabelFilter
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "basic find labels",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
			args: args{
				filter: platform.LabelFilter{},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "find labels filtering",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
					{
						ResourceID: MustIDBase16(bucketTwoID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				filter: platform.LabelFilter{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.TODO()
			labels, err := s.FindLabels(ctx, tt.args.filter)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}

			if diff := cmp.Diff(labels, tt.wants.labels, labelCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}

func UpdateLabel(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		label  platform.Label
		update platform.LabelUpdate
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "update label color",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
				update: platform.LabelUpdate{
					Color: &validColor,
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
						Color:      "fff000",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "invalid label color update",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
				update: platform.LabelUpdate{
					Color: &invalidColor,
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
						Color:      "fff000",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.TODO()
			_, err := s.UpdateLabel(ctx, &tt.args.label, tt.args.update)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}

			labels, err := s.FindLabels(ctx, platform.LabelFilter{})
			if err != nil {
				t.Fatalf("failed to retrieve labels: %v", err)
			}
			if diff := cmp.Diff(labels, tt.wants.labels, labelCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}

func DeleteLabel(
	init func(LabelFields, *testing.T) (platform.LabelService, func()),
	t *testing.T,
) {
	type args struct {
		label platform.Label
	}
	type wants struct {
		err    error
		labels []*platform.Label
	}

	tests := []struct {
		name   string
		fields LabelFields
		args   args
		wants  wants
	}{
		{
			name: "basic delete label",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
			args: args{
				label: platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag1",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag2",
					},
				},
			},
		},
		{
			name: "deleting a non-existant label",
			fields: LabelFields{
				Labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
			},
			args: args{
				label: platform.Label{
					ResourceID: MustIDBase16(bucketOneID),
					Name:       "Tag2",
				},
			},
			wants: wants{
				labels: []*platform.Label{
					{
						ResourceID: MustIDBase16(bucketOneID),
						Name:       "Tag1",
					},
				},
				err: platform.ErrLabelNotFound,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, done := init(tt.fields, t)
			defer done()
			ctx := context.TODO()
			err := s.DeleteLabel(ctx, tt.args.label)
			if (err != nil) != (tt.wants.err != nil) {
				t.Fatalf("expected error '%v' got '%v'", tt.wants.err, err)
			}

			if err != nil && tt.wants.err != nil {
				if err.Error() != tt.wants.err.Error() {
					t.Fatalf("expected error messages to match '%v' got '%v'", tt.wants.err, err.Error())
				}
			}

			labels, err := s.FindLabels(ctx, platform.LabelFilter{})
			if err != nil {
				t.Fatalf("failed to retrieve labels: %v", err)
			}
			if diff := cmp.Diff(labels, tt.wants.labels, labelCmpOptions...); diff != "" {
				t.Errorf("labels are different -got/+want\ndiff %s", diff)
			}
		})
	}
}
<|endoftext|>


<commit_before>package self_test

import (
	"crypto/tls"
	"fmt"
	"net"
	"time"

	quic "github.com/lucas-clemente/quic-go"
	"github.com/lucas-clemente/quic-go/integrationtests/tools/proxy"
	"github.com/lucas-clemente/quic-go/internal/protocol"
	"github.com/lucas-clemente/quic-go/internal/utils"
	"github.com/lucas-clemente/quic-go/qerr"

	"github.com/lucas-clemente/quic-go/internal/testdata"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Handshake RTT tests", func() {
	var (
		proxy         *quicproxy.QuicProxy
		server        quic.Listener
		serverConfig  *quic.Config
		testStartedAt time.Time
		acceptStopped chan struct{}
	)

	rtt := 400 * time.Millisecond

	BeforeEach(func() {
		acceptStopped = make(chan struct{})
		serverConfig = &quic.Config{}
	})

	AfterEach(func() {
		Expect(proxy.Close()).To(Succeed())
		Expect(server.Close()).To(Succeed())
		<-acceptStopped
	})

	runServerAndProxy := func() {
		var err error
		// start the server
		server, err = quic.ListenAddr("localhost:0", testdata.GetTLSConfig(), serverConfig)
		Expect(err).ToNot(HaveOccurred())
		// start the proxy
		proxy, err = quicproxy.NewQuicProxy("localhost:0", protocol.VersionWhatever, &quicproxy.Opts{
			RemoteAddr:  server.Addr().String(),
			DelayPacket: func(_ quicproxy.Direction, _ uint64) time.Duration { return rtt / 2 },
		})
		Expect(err).ToNot(HaveOccurred())

		testStartedAt = time.Now()

		go func() {
			defer GinkgoRecover()
			defer close(acceptStopped)
			for {
				_, err := server.Accept()
				if err != nil {
					return
				}
			}
		}()
	}

	expectDurationInRTTs := func(num int) {
		testDuration := time.Since(testStartedAt)
		expectedDuration := time.Duration(num) * rtt
		Expect(testDuration).To(SatisfyAll(
			BeNumerically(">=", expectedDuration),
			BeNumerically("<", expectedDuration+rtt),
		))
	}

	It("fails when there's no matching version, after 1 RTT", func() {
		Expect(len(protocol.SupportedVersions)).To(BeNumerically(">", 1))
		serverConfig.Versions = protocol.SupportedVersions[:1]
		runServerAndProxy()
		clientConfig := &quic.Config{
			Versions: protocol.SupportedVersions[1:2],
		}
		_, err := quic.DialAddr(proxy.LocalAddr().String(), nil, clientConfig)
		Expect(err).To(HaveOccurred())
		Expect(err.(qerr.ErrorCode)).To(Equal(qerr.InvalidVersion))
		expectDurationInRTTs(1)
	})

	// 1 RTT for verifying the source address
	// 1 RTT to become secure
	// 1 RTT to become forward-secure
	It("is forward-secure after 3 RTTs", func() {
		runServerAndProxy()
		_, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil)
		Expect(err).ToNot(HaveOccurred())
		expectDurationInRTTs(3)
	})

	It("does version negotiation in 1 RTT", func() {
		Expect(len(protocol.SupportedVersions)).To(BeNumerically(">", 1))
		// the server doesn't support the highest supported version, which is the first one the client will try
		serverConfig.Versions = protocol.SupportedVersions[1:]
		runServerAndProxy()
		_, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil)
		Expect(err).ToNot(HaveOccurred())
		expectDurationInRTTs(4)
	})

	// 1 RTT for verifying the source address
	// 1 RTT to become secure
	// TODO (marten-seemann): enable this test (see #625)
	PIt("is secure after 2 RTTs", func() {
		utils.SetLogLevel(utils.LogLevelDebug)
		runServerAndProxy()
		_, err := quic.DialAddrNonFWSecure(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil)
		fmt.Println("#### is non fw secure ###")
		Expect(err).ToNot(HaveOccurred())
		expectDurationInRTTs(2)
	})

	It("is forward-secure after 2 RTTs when the server doesn't require a Cookie", func() {
		serverConfig.AcceptCookie = func(_ net.Addr, _ *quic.Cookie) bool {
			return true
		}
		runServerAndProxy()
		_, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil)
		Expect(err).ToNot(HaveOccurred())
		expectDurationInRTTs(2)
	})

	It("doesn't complete the handshake when the server never accepts the Cookie", func() {
		serverConfig.AcceptCookie = func(_ net.Addr, _ *quic.Cookie) bool {
			return false
		}
		runServerAndProxy()
		_, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil)
		Expect(err).To(HaveOccurred())
		Expect(err.(*qerr.QuicError).ErrorCode).To(Equal(qerr.CryptoTooManyRejects))
	})

	It("doesn't complete the handshake when the handshake timeout is too short", func() {
		serverConfig.HandshakeTimeout = 2 * rtt
		runServerAndProxy()
		_, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil)
		Expect(err).To(HaveOccurred())
		Expect(err.(*qerr.QuicError).ErrorCode).To(Equal(qerr.HandshakeTimeout))
		// 2 RTTs during the timeout
		// plus 1 RTT: the timer starts 0.5 RTTs after sending the first packet, and the CONNECTION_CLOSE needs another 0.5 RTTs to reach the client
		expectDurationInRTTs(3)
	})
})
<commit_msg>add an integration test for invalid certificates<commit_after>package self_test

import (
	"crypto/tls"
	"fmt"
	"net"
	"time"

	quic "github.com/lucas-clemente/quic-go"
	"github.com/lucas-clemente/quic-go/integrationtests/tools/proxy"
	"github.com/lucas-clemente/quic-go/internal/protocol"
	"github.com/lucas-clemente/quic-go/internal/utils"
	"github.com/lucas-clemente/quic-go/qerr"

	"github.com/lucas-clemente/quic-go/internal/testdata"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Handshake RTT tests", func() {
	var (
		proxy         *quicproxy.QuicProxy
		server        quic.Listener
		serverConfig  *quic.Config
		testStartedAt time.Time
		acceptStopped chan struct{}
	)

	rtt := 400 * time.Millisecond

	BeforeEach(func() {
		acceptStopped = make(chan struct{})
		serverConfig = &quic.Config{}
	})

	AfterEach(func() {
		Expect(proxy.Close()).To(Succeed())
		Expect(server.Close()).To(Succeed())
		<-acceptStopped
	})

	runServerAndProxy := func() {
		var err error
		// start the server
		server, err = quic.ListenAddr("localhost:0", testdata.GetTLSConfig(), serverConfig)
		Expect(err).ToNot(HaveOccurred())
		// start the proxy
		proxy, err = quicproxy.NewQuicProxy("localhost:0", protocol.VersionWhatever, &quicproxy.Opts{
			RemoteAddr:  server.Addr().String(),
			DelayPacket: func(_ quicproxy.Direction, _ uint64) time.Duration { return rtt / 2 },
		})
		Expect(err).ToNot(HaveOccurred())

		testStartedAt = time.Now()

		go func() {
			defer GinkgoRecover()
			defer close(acceptStopped)
			for {
				_, err := server.Accept()
				if err != nil {
					return
				}
			}
		}()
	}

	expectDurationInRTTs := func(num int) {
		testDuration := time.Since(testStartedAt)
		expectedDuration := time.Duration(num) * rtt
		Expect(testDuration).To(SatisfyAll(
			BeNumerically(">=", expectedDuration),
			BeNumerically("<", expectedDuration+rtt),
		))
	}

	It("fails when there's no matching version, after 1 RTT", func() {
		Expect(len(protocol.SupportedVersions)).To(BeNumerically(">", 1))
		serverConfig.Versions = protocol.SupportedVersions[:1]
		runServerAndProxy()
		clientConfig := &quic.Config{
			Versions: protocol.SupportedVersions[1:2],
		}
		_, err := quic.DialAddr(proxy.LocalAddr().String(), nil, clientConfig)
		Expect(err).To(HaveOccurred())
		Expect(err.(qerr.ErrorCode)).To(Equal(qerr.InvalidVersion))
		expectDurationInRTTs(1)
	})

	// 1 RTT for verifying the source address
	// 1 RTT to become secure
	// 1 RTT to become forward-secure
	It("is forward-secure after 3 RTTs", func() {
		runServerAndProxy()
		_, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil)
		Expect(err).ToNot(HaveOccurred())
		expectDurationInRTTs(3)
	})

	It("does version negotiation in 1 RTT", func() {
		Expect(len(protocol.SupportedVersions)).To(BeNumerically(">", 1))
		// the server doesn't support the highest supported version, which is the first one the client will try
		serverConfig.Versions = protocol.SupportedVersions[1:]
		runServerAndProxy()
		_, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil)
		Expect(err).ToNot(HaveOccurred())
		expectDurationInRTTs(4)
	})

	// 1 RTT for verifying the source address
	// 1 RTT to become secure
	// TODO (marten-seemann): enable this test (see #625)
	PIt("is secure after 2 RTTs", func() {
		utils.SetLogLevel(utils.LogLevelDebug)
		runServerAndProxy()
		_, err := quic.DialAddrNonFWSecure(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil)
		fmt.Println("#### is non fw secure ###")
		Expect(err).ToNot(HaveOccurred())
		expectDurationInRTTs(2)
	})

	It("is forward-secure after 2 RTTs when the server doesn't require a Cookie", func() {
		serverConfig.AcceptCookie = func(_ net.Addr, _ *quic.Cookie) bool {
			return true
		}
		runServerAndProxy()
		_, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil)
		Expect(err).ToNot(HaveOccurred())
		expectDurationInRTTs(2)
	})

	It("doesn't complete the handshake when the server never accepts the Cookie", func() {
		serverConfig.AcceptCookie = func(_ net.Addr, _ *quic.Cookie) bool {
			return false
		}
		runServerAndProxy()
		_, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil)
		Expect(err).To(HaveOccurred())
		Expect(err.(*qerr.QuicError).ErrorCode).To(Equal(qerr.CryptoTooManyRejects))
	})

	It("doesn't complete the handshake when the handshake timeout is too short", func() {
		serverConfig.HandshakeTimeout = 2 * rtt
		runServerAndProxy()
		_, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil)
		Expect(err).To(HaveOccurred())
		Expect(err.(*qerr.QuicError).ErrorCode).To(Equal(qerr.HandshakeTimeout))
		// 2 RTTs during the timeout
		// plus 1 RTT: the timer starts 0.5 RTTs after sending the first packet, and the CONNECTION_CLOSE needs another 0.5 RTTs to reach the client
		expectDurationInRTTs(3)
	})

	It("errors when the client doesn't accept the certificate", func() {
		// don't validate the client's address, send the certificate in the first flight
		serverConfig.AcceptCookie = func(_ net.Addr, _ *quic.Cookie) bool {
			return true
		}
		runServerAndProxy()
		_, err := quic.DialAddr(proxy.LocalAddr().String(), nil, nil)
		Expect(err).To(MatchError(qerr.ProofInvalid))
		expectDurationInRTTs(1)
	})
})
<|endoftext|>


<commit_before>//
// templates.go
// template model interfaces
//
package srnd

import (
  "fmt"
  "github.com/hoisie/mustache"
  "io"
  "io/ioutil"
  "log"
  "path/filepath"
  "sort"
  "strings"
)

type templateEngine struct {
  // every newsgroup
  groups map[string]GroupModel
  // loaded templates
  templates map[string]string
  // root directory for templates
  template_dir string
}

func (self templateEngine) templateCached(name string) (ok bool) {
  _, ok = self.templates[name]
  return
}

// explicitly reload a template
func (self templateEngine) reloadTemplate(name string) {
  self.templates[name] = self.loadTemplate(name)
}

// check if we have this template
func (self templateEngine) hasTemplate(name string) bool {
  return CheckFile(self.templateFilepath(name))
}

// explicitly reload all loaded templates
func (self templateEngine) reloadAllTemplates() {
  loadThese := []string{}
  // get all the names of the templates we have loaded
  for tname, _ := range self.templates {
    loadThese = append(loadThese, tname)
  }
  // for each template we have loaded, reload the contents from file
  for _, tname := range loadThese {
    self.templates[tname] = self.loadTemplate(tname)
  }
}

// get the filepath to a template
func (self templateEngine) templateFilepath(name string) string {
  if strings.Count(name, "..") > 0 {
    return ""
  }
  return filepath.Join(self.template_dir, name)
}

// load a template from file, return as string
func (self templateEngine) loadTemplate(name string) (t string) {
  // ignores errors, this is probably bad
  b, _ := ioutil.ReadFile(self.templateFilepath(name))
  t = string(b)
  return
}

// get a template, if it's not cached load from file and cache it
func (self templateEngine) getTemplate(name string) (t string) {
  if self.templateCached(name) {
    t, _ = self.templates[name]
  } else {
    self.templates[name] = self.loadTemplate(name)
  }
  return
}

// render a template, self explanitory
func (self templateEngine) renderTemplate(name string, obj interface{}) string {
  t := self.getTemplate(name)
  return mustache.Render(t, obj)
}

// get a board model given a newsgroup
// load un updated board model if we don't have it
func (self templateEngine) obtainBoard(prefix, frontend, group string, db Database) (model GroupModel) {
  model, ok := self.groups[group]
  // if we don't already have the board loaded load it
  if ! ok  {
    p := db.GetGroupPageCount(group)
    pages := int(p)
    // ignore error
    perpage, _ := db.GetThreadsPerPage(group)
    for page := 0 ; page < pages ; page ++ {
      model = append(model, db.GetGroupForPage(prefix, frontend, group, page, int(perpage)))
    }
    self.groups[group] = model
  }
  return

}
// generate a board page
func (self templateEngine) genBoardPage(prefix, frontend, newsgroup string, page int, outfile string, db Database) {

  // get the board model
  board := self.obtainBoard(prefix, frontend, newsgroup, db)
  // update the board page
  board = board.Update(page, db)
  if page >= len(board) {
    log.Println("board page should not exist", newsgroup, "page", page)
    return
  }
  // render it
  wr, err := OpenFileWriter(outfile)
  if err == nil {
    board[page].RenderTo(wr)
    wr.Close()
    log.Println("wrote file", outfile)
  } else {
    log.Println("error generating board page", page, "for", newsgroup, err)
  }
  // save it
  self.groups[newsgroup] = board
}

// generate every page for a board
func (self templateEngine) genBoard(prefix, frontend, newsgroup, outdir string, db Database) {
  // get the board model
  board := self.obtainBoard(prefix, frontend, newsgroup, db)
  // update the entire board model
  board = board.UpdateAll(db)
  // save the model
  self.groups[newsgroup] = board

  pages := len(board)
  for page := 0 ; page < pages ; page ++ {
    outfile := filepath.Join(outdir, fmt.Sprintf("%s-%d.html", newsgroup, page))
    wr, err := OpenFileWriter(outfile)
    if err == nil {
      board[page].RenderTo(wr)
      wr.Close()
      log.Println("wrote file", outfile)
    } else {
      log.Println("error generating board page", page, "for", newsgroup, err)
    }
  }
}

func (self templateEngine) genUkko(prefix, frontend, outfile string, database Database) {
  var threads []ThreadModel
  // get the last 15 bumped threads globally, for each...
  for _, article := range database.GetLastBumpedThreads("", 15) {
    // get the newsgroup and root post id
    newsgroup, msgid := article[1], article[0]
    // obtain board model
    board := self.obtainBoard(prefix, frontend, newsgroup, database)
    // update first page
    board = board.Update(0, database)
    // grab the root post in question
    th := board[0].GetThread(msgid)
    if th != nil {
        threads = append(threads, th.Update(database))
    }
    // save board model
    self.groups[newsgroup] = board
  }
  wr, err := OpenFileWriter(outfile)
  if err == nil {
    io.WriteString(wr, template.renderTemplate("ukko.mustache", map[string]interface{} { "prefix" : prefix, "threads" : threads }))
    wr.Close()
  } else {
    log.Println("error generating ukko", err)
  }
}

func (self templateEngine) genThread(root ArticleEntry, prefix, frontend, outfile string, db Database) {
  newsgroup := root.Newsgroup()
  msgid := root.MessageID()
  var th ThreadModel
  // get the board model
  board := self.obtainBoard(prefix, frontend, newsgroup, db)
  // find the thread model in question
  for _, pagemodel := range board {
    t := pagemodel.GetThread(msgid)
    if t != nil {
      th = t
      break
    }
  }
  if th == nil {
    // wtf we don't have the thread?
    log.Println("we didn't find thread for", msgid, "did not regenerate")
  } else {
    // update thread model and write it out
    err := th.Update(db)
    if err == nil {
      wr, err := OpenFileWriter(outfile)
      if err == nil {
        th.RenderTo(wr)
        wr.Close()
        log.Println("wrote file", outfile)
      } else {
        log.Println("did not write", outfile, err)
      }
    } else {
      log.Println("failed to update thread", msgid, err)
    }
  }
  // save it
  self.groups[newsgroup] = board
}

func newTemplateEngine(dir string) *templateEngine {
  return &templateEngine{
    groups: make(map[string]GroupModel),
    templates: make(map[string]string),
    template_dir: dir,
  }
}

var template = newTemplateEngine(defaultTemplateDir())

func renderPostForm(prefix, board, op_msg_id string) string {
  url := prefix + "post/" + board
  button := "New Thread"
  if op_msg_id != "" {
    button = "Reply"
  }
  return template.renderTemplate("postform.mustache", map[string]string { "post_url" : url, "reference" : op_msg_id , "button" : button } )
}

func (self templateEngine) genFrontPage(top_count int, frontend_name, outfile string, db Database) {
  // the graph for the front page
  var frontpage_graph boardPageRows

  // for each group
  groups := db.GetAllNewsgroups()
  for idx, group := range groups {
    if idx >= top_count {
      break
    }
    // posts per hour
    hour := db.CountPostsInGroup(group, 3600)
    // posts per day
    day := db.CountPostsInGroup(group, 86400)
    // posts total
    all := db.CountPostsInGroup(group, 0)
    frontpage_graph = append(frontpage_graph, boardPageRow{
      All: all,
      Day: day,
      Hour: hour,
      Board: group,
    })
  }
  wr, err := OpenFileWriter(outfile)
  if err != nil {
    log.Println("cannot render front page", err)
    return
  }

  param := make(map[string]interface{})
  sort.Sort(frontpage_graph)
  param["graph"] = frontpage_graph
  param["frontend"] = frontend_name
  param["totalposts"] = db.ArticleCount()
  _, err = io.WriteString(wr, self.renderTemplate("frontpage.mustache", param))
  if err != nil {
    log.Println("error writing front page", err)
  }
  wr.Close()
}
<commit_msg>add error reporting in templates<commit_after>//
// templates.go
// template model interfaces
//
package srnd

import (
  "fmt"
  "github.com/hoisie/mustache"
  "io"
  "io/ioutil"
  "log"
  "path/filepath"
  "sort"
  "strings"
)

type templateEngine struct {
  // every newsgroup
  groups map[string]GroupModel
  // loaded templates
  templates map[string]string
  // root directory for templates
  template_dir string
}

func (self templateEngine) templateCached(name string) (ok bool) {
  _, ok = self.templates[name]
  return
}

// explicitly reload a template
func (self templateEngine) reloadTemplate(name string) {
  self.templates[name] = self.loadTemplate(name)
}

// check if we have this template
func (self templateEngine) hasTemplate(name string) bool {
  return CheckFile(self.templateFilepath(name))
}

// explicitly reload all loaded templates
func (self templateEngine) reloadAllTemplates() {
  loadThese := []string{}
  // get all the names of the templates we have loaded
  for tname, _ := range self.templates {
    loadThese = append(loadThese, tname)
  }
  // for each template we have loaded, reload the contents from file
  for _, tname := range loadThese {
    self.templates[tname] = self.loadTemplate(tname)
  }
}

// get the filepath to a template
func (self templateEngine) templateFilepath(name string) string {
  if strings.Count(name, "..") > 0 {
    return ""
  }
  return filepath.Join(self.template_dir, name)
}

// load a template from file, return as string
func (self templateEngine) loadTemplate(name string) (t string) {
  // ignores errors, this is probably bad
  b, err := ioutil.ReadFile(self.templateFilepath(name))
  if err != nil {
    log.Println("error loading template", err)
  }
  t = string(b)
  return
}

// get a template, if it's not cached load from file and cache it
func (self templateEngine) getTemplate(name string) (t string) {
  if self.templateCached(name) {
    t, _ = self.templates[name]
  } else {
    self.templates[name] = self.loadTemplate(name)
  }
  return
}

// render a template, self explanitory
func (self templateEngine) renderTemplate(name string, obj interface{}) string {
  t := self.getTemplate(name)
  return mustache.Render(t, obj)
}

// get a board model given a newsgroup
// load un updated board model if we don't have it
func (self templateEngine) obtainBoard(prefix, frontend, group string, db Database) (model GroupModel) {
  model, ok := self.groups[group]
  // if we don't already have the board loaded load it
  if ! ok  {
    p := db.GetGroupPageCount(group)
    pages := int(p)
    // ignore error
    perpage, _ := db.GetThreadsPerPage(group)
    for page := 0 ; page < pages ; page ++ {
      model = append(model, db.GetGroupForPage(prefix, frontend, group, page, int(perpage)))
    }
    self.groups[group] = model
  }
  return

}
// generate a board page
func (self templateEngine) genBoardPage(prefix, frontend, newsgroup string, page int, outfile string, db Database) {

  // get the board model
  board := self.obtainBoard(prefix, frontend, newsgroup, db)
  // update the board page
  board = board.Update(page, db)
  if page >= len(board) {
    log.Println("board page should not exist", newsgroup, "page", page)
    return
  }
  // render it
  wr, err := OpenFileWriter(outfile)
  if err == nil {
    board[page].RenderTo(wr)
    wr.Close()
    log.Println("wrote file", outfile)
  } else {
    log.Println("error generating board page", page, "for", newsgroup, err)
  }
  // save it
  self.groups[newsgroup] = board
}

// generate every page for a board
func (self templateEngine) genBoard(prefix, frontend, newsgroup, outdir string, db Database) {
  // get the board model
  board := self.obtainBoard(prefix, frontend, newsgroup, db)
  // update the entire board model
  board = board.UpdateAll(db)
  // save the model
  self.groups[newsgroup] = board

  pages := len(board)
  for page := 0 ; page < pages ; page ++ {
    outfile := filepath.Join(outdir, fmt.Sprintf("%s-%d.html", newsgroup, page))
    wr, err := OpenFileWriter(outfile)
    if err == nil {
      board[page].RenderTo(wr)
      wr.Close()
      log.Println("wrote file", outfile)
    } else {
      log.Println("error generating board page", page, "for", newsgroup, err)
    }
  }
}

func (self templateEngine) genUkko(prefix, frontend, outfile string, database Database) {
  var threads []ThreadModel
  // get the last 15 bumped threads globally, for each...
  for _, article := range database.GetLastBumpedThreads("", 15) {
    // get the newsgroup and root post id
    newsgroup, msgid := article[1], article[0]
    // obtain board model
    board := self.obtainBoard(prefix, frontend, newsgroup, database)
    // update first page
    board = board.Update(0, database)
    // grab the root post in question
    th := board[0].GetThread(msgid)
    if th != nil {
        threads = append(threads, th.Update(database))
    }
    // save board model
    self.groups[newsgroup] = board
  }
  wr, err := OpenFileWriter(outfile)
  if err == nil {
    io.WriteString(wr, template.renderTemplate("ukko.mustache", map[string]interface{} { "prefix" : prefix, "threads" : threads }))
    wr.Close()
  } else {
    log.Println("error generating ukko", err)
  }
}

func (self templateEngine) genThread(root ArticleEntry, prefix, frontend, outfile string, db Database) {
  newsgroup := root.Newsgroup()
  msgid := root.MessageID()
  var th ThreadModel
  // get the board model
  board := self.obtainBoard(prefix, frontend, newsgroup, db)
  // find the thread model in question
  for _, pagemodel := range board {
    t := pagemodel.GetThread(msgid)
    if t != nil {
      th = t
      break
    }
  }
  if th == nil {
    // wtf we don't have the thread?
    log.Println("we didn't find thread for", msgid, "did not regenerate")
  } else {
    // update thread model and write it out
    err := th.Update(db)
    if err == nil {
      wr, err := OpenFileWriter(outfile)
      if err == nil {
        th.RenderTo(wr)
        wr.Close()
        log.Println("wrote file", outfile)
      } else {
        log.Println("did not write", outfile, err)
      }
    } else {
      log.Println("failed to update thread", msgid, err)
    }
  }
  // save it
  self.groups[newsgroup] = board
}

func newTemplateEngine(dir string) *templateEngine {
  return &templateEngine{
    groups: make(map[string]GroupModel),
    templates: make(map[string]string),
    template_dir: dir,
  }
}

var template = newTemplateEngine(defaultTemplateDir())

func renderPostForm(prefix, board, op_msg_id string) string {
  url := prefix + "post/" + board
  button := "New Thread"
  if op_msg_id != "" {
    button = "Reply"
  }
  return template.renderTemplate("postform.mustache", map[string]string { "post_url" : url, "reference" : op_msg_id , "button" : button } )
}

func (self templateEngine) genFrontPage(top_count int, frontend_name, outfile string, db Database) {
  // the graph for the front page
  var frontpage_graph boardPageRows

  // for each group
  groups := db.GetAllNewsgroups()
  for idx, group := range groups {
    if idx >= top_count {
      break
    }
    // posts per hour
    hour := db.CountPostsInGroup(group, 3600)
    // posts per day
    day := db.CountPostsInGroup(group, 86400)
    // posts total
    all := db.CountPostsInGroup(group, 0)
    frontpage_graph = append(frontpage_graph, boardPageRow{
      All: all,
      Day: day,
      Hour: hour,
      Board: group,
    })
  }
  wr, err := OpenFileWriter(outfile)
  if err != nil {
    log.Println("cannot render front page", err)
    return
  }

  param := make(map[string]interface{})
  sort.Sort(frontpage_graph)
  param["graph"] = frontpage_graph
  param["frontend"] = frontend_name
  param["totalposts"] = db.ArticleCount()
  _, err = io.WriteString(wr, self.renderTemplate("frontpage.mustache", param))
  if err != nil {
    log.Println("error writing front page", err)
  }
  wr.Close()
}
<|endoftext|>


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

package sync_test

import (
	. "sync"
	"testing"
)

type one int

func (o *one) Increment() {
	*o++
}

func run(t *testing.T, once *Once, o *one, c chan bool) {
	once.Do(func() { o.Increment() })
	if v := *o; v != 1 {
		t.Errorf("once failed inside run: %d is not 1", v)
	}
	c <- true
}

func TestOnce(t *testing.T) {
	o := new(one)
	once := new(Once)
	c := make(chan bool)
	const N = 10
	for i := 0; i < N; i++ {
		go run(t, once, o, c)
	}
	for i := 0; i < N; i++ {
		<-c
	}
	if *o != 1 {
		t.Errorf("once failed outside run: %d is not 1", *o)
	}
}

func TestOncePanic(t *testing.T) {
	once := new(Once)
	for i := 0; i < 2; i++ {
		func() {
			defer func() {
				r := recover()
				if r == nil && i == 0 {
					t.Fatalf("Once.Do() has not panic'ed on first iteration")
				}
				if r != nil && i == 1 {
					t.Fatalf("Once.Do() has panic'ed on second iteration")
				}
			}()
			once.Do(func() {
				panic("failed")
			})
		}()
	}
	once.Do(func() {})
	once.Do(func() {
		t.Fatalf("Once called twice")
	})
}

func BenchmarkOnce(b *testing.B) {
	var once Once
	f := func() {}
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			once.Do(f)
		}
	})
}
<commit_msg>sync: simplify TestOncePanic<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sync_test

import (
	. "sync"
	"testing"
)

type one int

func (o *one) Increment() {
	*o++
}

func run(t *testing.T, once *Once, o *one, c chan bool) {
	once.Do(func() { o.Increment() })
	if v := *o; v != 1 {
		t.Errorf("once failed inside run: %d is not 1", v)
	}
	c <- true
}

func TestOnce(t *testing.T) {
	o := new(one)
	once := new(Once)
	c := make(chan bool)
	const N = 10
	for i := 0; i < N; i++ {
		go run(t, once, o, c)
	}
	for i := 0; i < N; i++ {
		<-c
	}
	if *o != 1 {
		t.Errorf("once failed outside run: %d is not 1", *o)
	}
}

func TestOncePanic(t *testing.T) {
	var once Once
	func() {
		defer func() {
			if r := recover(); r == nil {
				t.Fatalf("Once.Do did not panic")
			}
		}()
		once.Do(func() {
			panic("failed")
		})
	}()

	once.Do(func() {
		t.Fatalf("Once.Do called twice")
	})
}

func BenchmarkOnce(b *testing.B) {
	var once Once
	f := func() {}
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			once.Do(f)
		}
	})
}
<|endoftext|>