<commit_before>package services_state

import (
	"encoding/json"
	"fmt"
	"reflect"
	"sync"
	"testing"
	"regexp"
	"time"

	. "github.com/smartystreets/goconvey/convey"
	"github.com/newrelic/bosun/director"
	"github.com/newrelic/bosun/service"
)

var hostname = "shakespeare"
var anotherHostname = "chaucer"

func Test_NewServer(t *testing.T) {

	Convey("Invoking NewServer()", t, func() {
		Convey("Returns a server with the correct name", func() {
			server := NewServer(hostname)
			So(server.Name, ShouldEqual, hostname)
		})

		Convey("Initializes the map", func() {
			server := NewServer(hostname)
			So(server.Services, ShouldNotBeNil)
		})

		Convey("Initializes the time", func() {
			server := NewServer(hostname)
			So(server.LastUpdated, ShouldBeTheSameTimeAs, time.Unix(0, 0))
		})

		Convey("Initializes the LastChanged", func() {
			server := NewServer(hostname)
			So(server.LastChanged, ShouldBeTheSameTimeAs, time.Unix(0, 0))
		})
	})
}

func Test_NewServicesState(t *testing.T) {

	Convey("Invoking NewServicesState()", t, func() {

		Convey("Initializes the Servers map", func() {
			state := NewServicesState()
			So(state.Servers, ShouldNotBeNil)
		})

		Convey("Initializes LastChanged", func() {
			state := NewServicesState()
			So(state.LastChanged, ShouldBeTheSameTimeAs, time.Unix(0, 0))
		})

	})
}

func Test_ServicesStateWithData(t *testing.T) {

	Convey("When working with data", t, func() {
		state := NewServicesState()
		state.Servers[hostname] = NewServer(hostname)

		baseTime := time.Now().UTC()

		svcId := "deadbeef123"

		svc := service.Service{
			ID: svcId,
			Name: "radical_service",
			Image: "101deadbeef",
			Created: baseTime,
			Hostname: anotherHostname,
			Updated: baseTime,
			Status: service.ALIVE,
		}

		Convey("Encode() generates JSON that we can Decode()", func() {
			decoded, err := Decode(state.Encode())

			So(err, ShouldBeNil)
			So(decoded.Servers[hostname].Name, ShouldEqual, hostname)
			So(len(decoded.Servers), ShouldEqual, 1)
		})

		Convey("Decode() returns an error when handed junk", func() {
			result, err := Decode([]byte("asdf"))

			So(result.Servers, ShouldBeEmpty)
			So(err, ShouldNotBeNil)
		})

		Convey("HasServer() is true when a server exists", func() {
			So(state.HasServer(hostname), ShouldBeTrue)
		})

		Convey("HasServer() is false when a server is missing", func() {
			So(state.HasServer("junk"), ShouldBeFalse)
		})

		Convey("GetLocalService() returns a service when we have it", func() {
			state.HostnameFn = func() (string, error) { return anotherHostname, nil }
			state.AddServiceEntry(svc)

			So(reflect.DeepEqual(state.GetLocalService(svcId), &svc), ShouldBeTrue)
		})

		Convey("AddServiceEntry()", func() {
			Convey("Merges in a new service", func() {
				So(state.HasServer(anotherHostname), ShouldBeFalse)

				state.AddServiceEntry(svc)

				So(state.HasServer(anotherHostname), ShouldBeTrue)
				So(state.Servers[anotherHostname].Services[svc.ID], ShouldNotBeNil)
			})

			Convey("Doesn't merge a stale service", func() {
				state.AddServiceEntry(svc)

				staleService := service.Service{
					ID: "deadbeef123",
					Name: "stale_service",
					Image: "stale",
					Created: baseTime,
					Hostname: anotherHostname,
					Updated: baseTime.Add(0 - 1 * time.Minute),
					Status: service.ALIVE,
				}

				state.AddServiceEntry(staleService)

				So(state.HasServer(anotherHostname), ShouldBeTrue)
				So(state.Servers[anotherHostname].Services[svc.ID].Updated,
					ShouldBeTheSameTimeAs, baseTime)
				So(state.Servers[anotherHostname].Services[svc.ID].Image,
					ShouldEqual, "101deadbeef")
			})

			Convey("Updates the LastUpdated time for the server", func() {
				newDate := svc.Updated.AddDate(0, 0, 5)
				svc.Updated = newDate
				state.AddServiceEntry(svc)

				So(state.Servers[anotherHostname].LastUpdated, ShouldBeTheSameTimeAs, newDate)
			})

			Convey("Updates the LastChanged time for a service when new", func() {
				lastChanged := state.LastChanged
				state.AddServiceEntry(svc)

				So(state.LastChanged.After(lastChanged), ShouldBeTrue)
				So(
					state.Servers[anotherHostname].LastChanged.After(lastChanged),
					ShouldBeTrue,
				)
			})

			Convey("Updates the LastChanged time for a service when changing", func() {
				state.AddServiceEntry(svc)
				lastChanged := state.LastChanged
				svc.Tombstone()
				state.AddServiceEntry(svc)

				So(state.LastChanged.After(lastChanged), ShouldBeTrue)
			})

			Convey("Skips LastChanged time for a service that didn't change", func() {
				state.AddServiceEntry(svc)
				lastChanged := state.LastChanged
				svc.Updated = time.Now().UTC()
				state.AddServiceEntry(svc)

				So(state.LastChanged.After(lastChanged), ShouldBeFalse)
			})

			Convey("Retransmits a packet when the state changes", func() {
				state.AddServiceEntry(svc)
				svc.Tombstone()
				state.AddServiceEntry(svc)

				packet := <-state.Broadcasts
				encoded, _ := svc.Encode()
				So(len(packet), ShouldEqual, 1)
				So(string(packet[0]), ShouldEqual, string(encoded))
			})

			Convey("Doesn't retransmit when the state is the same", func() {
				state.AddServiceEntry(svc)
				svc.Updated = svc.Updated.Add(1 * time.Second)
				state.AddServiceEntry(svc)
				time.Sleep(2 * time.Millisecond)

				pendingBroadcast := false
				select {
					case <-state.Broadcasts:
						pendingBroadcast = true
					default:
				}
				So(pendingBroadcast, ShouldBeFalse)
			})
		})

		Convey("Merge() merges state we care about from other state structs", func() {
			firstState  := NewServicesState()
			secondState := NewServicesState()
			firstState.AddServiceEntry(svc)<commit_msg>Use buffered channel and remove Sleep.<commit_after>package services_state

import (
	"encoding/json"
	"fmt"
	"reflect"
	"sync"
	"testing"
	"regexp"
	"time"

	. "github.com/smartystreets/goconvey/convey"
	"github.com/newrelic/bosun/director"
	"github.com/newrelic/bosun/service"
)

var hostname = "shakespeare"
var anotherHostname = "chaucer"

func Test_NewServer(t *testing.T) {

	Convey("Invoking NewServer()", t, func() {
		Convey("Returns a server with the correct name", func() {
			server := NewServer(hostname)
			So(server.Name, ShouldEqual, hostname)
		})

		Convey("Initializes the map", func() {
			server := NewServer(hostname)
			So(server.Services, ShouldNotBeNil)
		})

		Convey("Initializes the time", func() {
			server := NewServer(hostname)
			So(server.LastUpdated, ShouldBeTheSameTimeAs, time.Unix(0, 0))
		})

		Convey("Initializes the LastChanged", func() {
			server := NewServer(hostname)
			So(server.LastChanged, ShouldBeTheSameTimeAs, time.Unix(0, 0))
		})
	})
}

func Test_NewServicesState(t *testing.T) {

	Convey("Invoking NewServicesState()", t, func() {

		Convey("Initializes the Servers map", func() {
			state := NewServicesState()
			So(state.Servers, ShouldNotBeNil)
		})

		Convey("Initializes LastChanged", func() {
			state := NewServicesState()
			So(state.LastChanged, ShouldBeTheSameTimeAs, time.Unix(0, 0))
		})

	})
}

func Test_ServicesStateWithData(t *testing.T) {

	Convey("When working with data", t, func() {
		state := NewServicesState()
		state.Servers[hostname] = NewServer(hostname)

		baseTime := time.Now().UTC()

		svcId := "deadbeef123"

		svc := service.Service{
			ID: svcId,
			Name: "radical_service",
			Image: "101deadbeef",
			Created: baseTime,
			Hostname: anotherHostname,
			Updated: baseTime,
			Status: service.ALIVE,
		}

		Convey("Encode() generates JSON that we can Decode()", func() {
			decoded, err := Decode(state.Encode())

			So(err, ShouldBeNil)
			So(decoded.Servers[hostname].Name, ShouldEqual, hostname)
			So(len(decoded.Servers), ShouldEqual, 1)
		})

		Convey("Decode() returns an error when handed junk", func() {
			result, err := Decode([]byte("asdf"))

			So(result.Servers, ShouldBeEmpty)
			So(err, ShouldNotBeNil)
		})

		Convey("HasServer() is true when a server exists", func() {
			So(state.HasServer(hostname), ShouldBeTrue)
		})

		Convey("HasServer() is false when a server is missing", func() {
			So(state.HasServer("junk"), ShouldBeFalse)
		})

		Convey("GetLocalService() returns a service when we have it", func() {
			state.HostnameFn = func() (string, error) { return anotherHostname, nil }
			state.AddServiceEntry(svc)

			So(reflect.DeepEqual(state.GetLocalService(svcId), &svc), ShouldBeTrue)
		})

		Convey("AddServiceEntry()", func() {
			Convey("Merges in a new service", func() {
				So(state.HasServer(anotherHostname), ShouldBeFalse)

				state.AddServiceEntry(svc)

				So(state.HasServer(anotherHostname), ShouldBeTrue)
				So(state.Servers[anotherHostname].Services[svc.ID], ShouldNotBeNil)
			})

			Convey("Doesn't merge a stale service", func() {
				state.AddServiceEntry(svc)

				staleService := service.Service{
					ID: "deadbeef123",
					Name: "stale_service",
					Image: "stale",
					Created: baseTime,
					Hostname: anotherHostname,
					Updated: baseTime.Add(0 - 1 * time.Minute),
					Status: service.ALIVE,
				}

				state.AddServiceEntry(staleService)

				So(state.HasServer(anotherHostname), ShouldBeTrue)
				So(state.Servers[anotherHostname].Services[svc.ID].Updated,
					ShouldBeTheSameTimeAs, baseTime)
				So(state.Servers[anotherHostname].Services[svc.ID].Image,
					ShouldEqual, "101deadbeef")
			})

			Convey("Updates the LastUpdated time for the server", func() {
				newDate := svc.Updated.AddDate(0, 0, 5)
				svc.Updated = newDate
				state.AddServiceEntry(svc)

				So(state.Servers[anotherHostname].LastUpdated, ShouldBeTheSameTimeAs, newDate)
			})

			Convey("Updates the LastChanged time for a service when new", func() {
				lastChanged := state.LastChanged
				state.AddServiceEntry(svc)

				So(state.LastChanged.After(lastChanged), ShouldBeTrue)
				So(
					state.Servers[anotherHostname].LastChanged.After(lastChanged),
					ShouldBeTrue,
				)
			})

			Convey("Updates the LastChanged time for a service when changing", func() {
				state.AddServiceEntry(svc)
				lastChanged := state.LastChanged
				svc.Tombstone()
				state.AddServiceEntry(svc)

				So(state.LastChanged.After(lastChanged), ShouldBeTrue)
			})

			Convey("Skips LastChanged time for a service that didn't change", func() {
				state.AddServiceEntry(svc)
				lastChanged := state.LastChanged
				svc.Updated = time.Now().UTC()
				state.AddServiceEntry(svc)

				So(state.LastChanged.After(lastChanged), ShouldBeFalse)
			})

			Convey("Retransmits a packet when the state changes", func() {
				state.AddServiceEntry(svc)
				svc.Tombstone()
				state.AddServiceEntry(svc)

				packet := <-state.Broadcasts
				encoded, _ := svc.Encode()
				So(len(packet), ShouldEqual, 1)
				So(string(packet[0]), ShouldEqual, string(encoded))
			})

			Convey("Doesn't retransmit when the state is the same", func() {
				state.Broadcasts = make(chan [][]byte, 1)
				state.AddServiceEntry(svc)
				svc.Updated = svc.Updated.Add(1 * time.Second)
				state.AddServiceEntry(svc)

				pendingBroadcast := false
				select {
					case <-state.Broadcasts:
						pendingBroadcast = true
					default:
				}
				So(pendingBroadcast, ShouldBeFalse)
			})
		})

		Convey("Merge() merges state we care about from other state structs", func() {
			firstState  := NewServicesState()
			secondState := NewServicesState()
			firstState.AddServiceEntry(svc)
{'fraction_non_alphanumeric': 0.13382274898284097, 'fraction_numerical': 0.004953122235980895, 'mean_word_length': 4.070403587443947, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2476547', 'n_tokens_mistral': 4539, 'n_tokens_neox': 3747, 'n_words': 772}