diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 960f3b4..e0d1604 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -27,4 +27,4 @@ jobs: - uses: actions/setup-go@v5 with: go-version-file: go.mod - - uses: golangci/golangci-lint-action@v6.5.2 + - uses: golangci/golangci-lint-action@v7.0.0 diff --git a/cmdStartWaiter/cmdStartWaiter.go b/cmdStartWaiter/cmdStartWaiter.go index 9327e77..dd0d402 100644 --- a/cmdStartWaiter/cmdStartWaiter.go +++ b/cmdStartWaiter/cmdStartWaiter.go @@ -4,8 +4,9 @@ import ( "io" ) -// CmdStartWaiter is a subset of the interface satisfied by exec.Cmd //go:generate counterfeiter . CmdStartWaiter + +// CmdStartWaiter is a subset of the interface satisfied by exec.Cmd type CmdStartWaiter interface { Start() error Wait() error diff --git a/config/config.go b/config/config.go index 6def9df..6323989 100644 --- a/config/config.go +++ b/config/config.go @@ -62,12 +62,12 @@ func Load(filename string) (*Config, error) { func (c Config) Validate() error { if c.OptionalTests.RunAppSyslogAvailability { if c.CF != nil && (c.CF.TCPDomain == "" || c.CF.AvailablePort == 0) { - return errors.New("`cf.tcp_domain` and `cf.available_port` must be set in order to run App Syslog Availability tests.") + return errors.New("`cf.tcp_domain` and `cf.available_port` must be set in order to run App Syslog Availability tests") } } if c.OptionalTests.RunTcpAvailability { if c.CF != nil && (c.CF.TCPDomain == "" || c.CF.TCPPort == 0) { - return errors.New("`cf.tcp_domain` and `cf.tcp_port` must be set in order to run TCP Availability tests.") + return errors.New("`cf.tcp_domain` and `cf.tcp_port` must be set in order to run TCP Availability tests") } } return nil diff --git a/config/config_suite_test.go b/config/config_suite_test.go index 4d4d240..c6e29ba 100644 --- a/config/config_suite_test.go +++ b/config/config_suite_test.go @@ -1,10 +1,10 @@ package config_test import ( + "testing" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - "testing" ) func TestConfig(t *testing.T) { diff --git a/config/config_test.go b/config/config_test.go deleted file mode 100644 index 3cc4866..0000000 --- a/config/config_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package config_test - -import ( - "os" - - "github.com/cloudfoundry/uptimer/config" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Config", func() { - var ( - configFile *os.File - err error - ) - - BeforeEach(func() { - configFile, err = os.CreateTemp("", "config") - Expect(err).ToNot(HaveOccurred()) - }) - - AfterEach(func() { - err = os.Remove(configFile.Name()) - Expect(err).ToNot(HaveOccurred()) - }) - - Describe("#Validate", func() { - It("Returns no error if run_tcp_availability is set to true and tcp_domain and tcp_port are provided", func() { - cfg := config.Config{ - CF: &config.Cf{ - TCPDomain: "tcp.my-cf.com", - TCPPort: 1025, - }, - OptionalTests: config.OptionalTests{RunTcpAvailability: true}, - } - - err := cfg.Validate() - Expect(err).ToNot(HaveOccurred()) - }) - - It("Returns error if run_tcp_availability is set to true, but tcp_domain and available_port are not provided", func() { - cfg := config.Config{ - CF: &config.Cf{}, - OptionalTests: config.OptionalTests{RunTcpAvailability: true}, - } - - err := cfg.Validate() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("`cf.tcp_domain` and `cf.tcp_port` must be set in order to run TCP Availability tests.")) - }) - - It("Returns error if run_tcp_availability is set to true, but tcp_port is not provided", func() { - cfg := config.Config{ - CF: &config.Cf{ - TCPDomain: "tcp.my-cf.com", - }, - OptionalTests: config.OptionalTests{RunTcpAvailability: true}, - } - - err := cfg.Validate() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("`cf.tcp_domain` and `cf.tcp_port` must be set in order to run TCP Availability tests.")) - }) - - It("Returns error if run_app_syslog_availability is set to true, but tcp_domain is not provided", func() { - cfg := config.Config{ - CF: &config.Cf{ - AvailablePort: 1025, - }, - OptionalTests: config.OptionalTests{RunAppSyslogAvailability: true}, - } - - err := cfg.Validate() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("`cf.tcp_domain` and `cf.available_port` must be set in order to run App Syslog Availability tests.")) - }) - It("Returns no error if run_app_syslog_availability is set to true and tcp_domain and available_port are not provided", func() { - cfg := config.Config{ - CF: &config.Cf{ - TCPDomain: "tcp.my-cf.com", - AvailablePort: 1025, - }, - OptionalTests: config.OptionalTests{RunAppSyslogAvailability: true}, - } - - err := cfg.Validate() - Expect(err).ToNot(HaveOccurred()) - }) - - It("Returns error if run_app_syslog_availability is set to true, but tcp_domain and available_port are not provided", func() { - cfg := config.Config{ - CF: &config.Cf{}, - OptionalTests: config.OptionalTests{RunAppSyslogAvailability: true}, - } - - err := cfg.Validate() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("`cf.tcp_domain` and `cf.available_port` must be set in order to run App Syslog Availability tests.")) - }) - - It("Returns error if run_app_syslog_availability is set to true, but available_port is not provided", func() { - cfg := config.Config{ - CF: &config.Cf{ - TCPDomain: "tcp.my-cf.com", - }, - OptionalTests: config.OptionalTests{RunAppSyslogAvailability: true}, - } - - err := cfg.Validate() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("`cf.tcp_domain` and `cf.available_port` must be set in order to run App Syslog Availability tests.")) - }) - - It("Returns error if run_app_syslog_availability is set to true, but tcp_domain is not provided", func() { - cfg := config.Config{ - CF: &config.Cf{ - AvailablePort: 1025, - }, - OptionalTests: config.OptionalTests{RunAppSyslogAvailability: true}, - } - - err := cfg.Validate() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("`cf.tcp_domain` and `cf.available_port` must be set in order to run App Syslog Availability tests.")) - }) - }) -}) diff --git a/config/validate_test.go b/config/validate_test.go new file mode 100644 index 0000000..a9ae5b1 --- /dev/null +++ b/config/validate_test.go @@ -0,0 +1,118 @@ +package config_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cloudfoundry/uptimer/config" +) + +var _ = Describe("Validate", func() { + var ( + cfg config.Config + + err error + ) + + Context("when measuring TCP availability", func() { + BeforeEach(func() { + cfg = config.Config{ + CF: &config.Cf{ + TCPDomain: "tcp.my-cf.com", + TCPPort: 1025, + }, + OptionalTests: config.OptionalTests{RunTcpAvailability: true}, + } + }) + + JustBeforeEach(func() { + err = cfg.Validate() + }) + + It("succeeds", func() { + Expect(err).ToNot(HaveOccurred()) + }) + + Context("when neither a TCP port or domain are provided", func() { + BeforeEach(func() { + cfg.CF.TCPDomain = "" + cfg.CF.TCPPort = 0 + }) + + It("returns an error", func() { + Expect(err).To(MatchError("`cf.tcp_domain` and `cf.tcp_port` must be set in order to run TCP Availability tests")) + }) + }) + + Context("when a TCP domain is not provided", func() { + BeforeEach(func() { + cfg.CF.TCPDomain = "" + }) + + It("returns an error", func() { + Expect(err).To(MatchError("`cf.tcp_domain` and `cf.tcp_port` must be set in order to run TCP Availability tests")) + }) + }) + + Context("when a TCP port is not provided", func() { + BeforeEach(func() { + cfg.CF.TCPPort = 0 + }) + + It("returns an error", func() { + Expect(err).To(MatchError("`cf.tcp_domain` and `cf.tcp_port` must be set in order to run TCP Availability tests")) + }) + }) + }) + + Context("when measuring app syslog availability", func() { + BeforeEach(func() { + cfg = config.Config{ + CF: &config.Cf{ + TCPDomain: "tcp.my-cf.com", + AvailablePort: 1025, + }, + OptionalTests: config.OptionalTests{RunAppSyslogAvailability: true}, + } + }) + + JustBeforeEach(func() { + err = cfg.Validate() + }) + + It("succeeds", func() { + Expect(err).ToNot(HaveOccurred()) + }) + + Context("when neither a TCP domain or available port are provided", func() { + BeforeEach(func() { + cfg.CF.TCPDomain = "" + cfg.CF.AvailablePort = 0 + }) + + It("returns an error", func() { + Expect(err).To(MatchError("`cf.tcp_domain` and `cf.available_port` must be set in order to run App Syslog Availability tests")) + }) + }) + + Context("when a TCP domain is not provided", func() { + BeforeEach(func() { + cfg.CF.TCPDomain = "" + }) + + It("returns an error", func() { + Expect(err).To(MatchError("`cf.tcp_domain` and `cf.available_port` must be set in order to run App Syslog Availability tests")) + }) + }) + + Context("when an available port is not provided", func() { + BeforeEach(func() { + cfg.CF.AvailablePort = 0 + }) + + It("returns an error", func() { + Expect(err).To(MatchError("`cf.tcp_domain` and `cf.available_port` must be set in order to run App Syslog Availability tests")) + }) + }) + }) +}) diff --git a/main.go b/main.go index 3dd76eb..eac7876 100644 --- a/main.go +++ b/main.go @@ -74,7 +74,7 @@ func main() { performMeasurements = false } logger.Println("Finished preparing included app") - defer os.RemoveAll(appPath) + defer os.RemoveAll(appPath) //nolint:errcheck var tcpPath string if cfg.OptionalTests.RunTcpAvailability { @@ -85,7 +85,7 @@ func main() { performMeasurements = false } logger.Println("Finished preparing included tcp app") - defer os.RemoveAll(tcpPath) + defer os.RemoveAll(tcpPath) //nolint:errcheck } var sinkAppPath string @@ -290,27 +290,30 @@ func prepareIncludedApp(name, source string) (string, error) { return "", err } - if err := os.WriteFile(filepath.Join(dir, "main.go"), []byte(source), 0644); err != nil { - os.RemoveAll(dir) - return "", err - } + defer func() { + if err != nil { + os.RemoveAll(dir) //nolint:errcheck + } + }() - manifest := goManifest(name) - if err := os.WriteFile(filepath.Join(dir, "manifest.yml"), []byte(manifest), 0644); err != nil { - os.RemoveAll(dir) + err = os.WriteFile(filepath.Join(dir, "main.go"), []byte(source), 0644) + if err != nil { return "", err } - return dir, nil -} - -func goManifest(appName string) string { - return fmt.Sprintf(`applications: + manifest := fmt.Sprintf(`applications: - name: %s memory: 64M disk: 16M env: - GOPACKAGENAME: github.com/cloudfoundry/uptimer/%s`, appName, appName) + GOPACKAGENAME: github.com/cloudfoundry/uptimer/%s`, name, name) + + err = os.WriteFile(filepath.Join(dir, "manifest.yml"), []byte(manifest), 0644) + if err != nil { + return "", err + } + + return dir, nil } func createWorkflow(cfc *config.Cf, appPath string, useQuotas bool) cfWorkflow.CfWorkflow { diff --git a/main_test.go b/main_test.go index 529db96..2aa0b18 100644 --- a/main_test.go +++ b/main_test.go @@ -46,7 +46,7 @@ var _ = Describe("uptimer", func() { tmpDir := GinkgoT().TempDir() f, err := os.Create(tmpDir + "/config.json") Expect(err).NotTo(HaveOccurred()) - defer f.Close() + defer f.Close() //nolint:errcheck b, err := json.Marshal(cfg) Expect(err).NotTo(HaveOccurred()) _, err = f.Write(b) @@ -74,7 +74,7 @@ var _ = Describe("uptimer", func() { }) It("prints an error", func() { - Expect(session.Out).To(Say("`cf.tcp_domain` and `cf.available_port` must be set in order to run App Syslog Availability tests.")) + Expect(session.Out).To(Say("`cf.tcp_domain` and `cf.available_port` must be set in order to run App Syslog Availability tests")) }) }) }) @@ -96,7 +96,7 @@ var _ = Describe("uptimer", func() { }) It("prints an error", func() { - Expect(session.Out).To(Say("`cf.tcp_domain` and `cf.tcp_port` must be set in order to run TCP Availability tests.")) + Expect(session.Out).To(Say("`cf.tcp_domain` and `cf.tcp_port` must be set in order to run TCP Availability tests")) }) }) }) diff --git a/measurement/availability.go b/measurement/availability.go index 57efc1e..00d7152 100644 --- a/measurement/availability.go +++ b/measurement/availability.go @@ -26,7 +26,7 @@ func (a *availability) PerformMeasurement() (string, string, string, bool) { if err != nil { return err.Error(), "", "", false } - defer res.Body.Close() + defer res.Body.Close() //nolint:errcheck if res.StatusCode != http.StatusOK { buf := new(bytes.Buffer) diff --git a/measurement/periodic_test.go b/measurement/periodic_test.go index 66d2458..966a948 100644 --- a/measurement/periodic_test.go +++ b/measurement/periodic_test.go @@ -6,11 +6,12 @@ import ( "time" "github.com/benbjohnson/clock" - . "github.com/cloudfoundry/uptimer/measurement" - "github.com/cloudfoundry/uptimer/measurement/measurementfakes" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + "github.com/cloudfoundry/uptimer/measurement" + "github.com/cloudfoundry/uptimer/measurement/measurementfakes" ) var _ = Describe("Periodic", func() { @@ -24,7 +25,7 @@ var _ = Describe("Periodic", func() { allowedFailures int shouldRetry bool - p Measurement + p measurement.Measurement ) BeforeEach(func() { @@ -39,7 +40,7 @@ var _ = Describe("Periodic", func() { allowedFailures = 0 shouldRetry = false - p = NewPeriodic( + p = measurement.NewPeriodic( logger, mockClock, freq, @@ -78,7 +79,7 @@ var _ = Describe("Periodic", func() { Context("without measure immediately", func() { BeforeEach(func() { - p = NewPeriodicWithoutMeasuringImmediately( + p = measurement.NewPeriodicWithoutMeasuringImmediately( logger, mockClock, freq, @@ -109,7 +110,7 @@ var _ = Describe("Periodic", func() { fakeResultSet.FailedReturns(2) allowedFailures = 4 - p = NewPeriodic( + p = measurement.NewPeriodic( logger, mockClock, freq, @@ -204,7 +205,7 @@ var _ = Describe("Periodic", func() { fakeResultSet.FailedReturns(1) allowedFailures = 2 - p = NewPeriodic( + p = measurement.NewPeriodic( logger, mockClock, freq, @@ -320,7 +321,7 @@ var _ = Describe("Periodic", func() { Describe("Failed", func() { BeforeEach(func() { - p = NewPeriodic(logger, mockClock, freq, fakeBaseMeasurement, fakeResultSet, 5, func(string, string) bool { return shouldRetry }) + p = measurement.NewPeriodic(logger, mockClock, freq, fakeBaseMeasurement, fakeResultSet, 5, func(string, string) bool { return shouldRetry }) }) It("Returns true if failure count > allowed number of failures", func() { @@ -348,7 +349,7 @@ var _ = Describe("Periodic", func() { succeeded := 3 allowedFailures := 3 - p = NewPeriodic(logger, mockClock, freq, fakeBaseMeasurement, fakeResultSet, allowedFailures, func(string, string) bool { return shouldRetry }) + p = measurement.NewPeriodic(logger, mockClock, freq, fakeBaseMeasurement, fakeResultSet, allowedFailures, func(string, string) bool { return shouldRetry }) fakeResultSet.FailedReturns(failed) fakeResultSet.SuccessfulReturns(succeeded) fakeResultSet.TotalReturns(failed + succeeded) @@ -368,7 +369,7 @@ var _ = Describe("Periodic", func() { succeeded := 4 allowedFailures := 2 - p = NewPeriodic(logger, mockClock, freq, fakeBaseMeasurement, fakeResultSet, allowedFailures, func(string, string) bool { return shouldRetry }) + p = measurement.NewPeriodic(logger, mockClock, freq, fakeBaseMeasurement, fakeResultSet, allowedFailures, func(string, string) bool { return shouldRetry }) fakeResultSet.FailedReturns(failed) fakeResultSet.SuccessfulReturns(succeeded) fakeResultSet.TotalReturns(failed + succeeded) @@ -388,7 +389,7 @@ var _ = Describe("Periodic", func() { succeeded := 1 allowedFailures := 2 - p = NewPeriodic(logger, mockClock, freq, fakeBaseMeasurement, fakeResultSet, allowedFailures, func(string, string) bool { return shouldRetry }) + p = measurement.NewPeriodic(logger, mockClock, freq, fakeBaseMeasurement, fakeResultSet, allowedFailures, func(string, string) bool { return shouldRetry }) fakeResultSet.FailedReturns(failed) fakeResultSet.SuccessfulReturns(succeeded) fakeResultSet.TotalReturns(failed + succeeded) @@ -410,12 +411,12 @@ var _ = Describe("Periodic", func() { succeeded := 3 allowedFailures := 3 - p = NewPeriodic(logger, mockClock, freq, fakeBaseMeasurement, fakeResultSet, allowedFailures, func(string, string) bool { return shouldRetry }) + p = measurement.NewPeriodic(logger, mockClock, freq, fakeBaseMeasurement, fakeResultSet, allowedFailures, func(string, string) bool { return shouldRetry }) fakeResultSet.FailedReturns(failed) fakeResultSet.SuccessfulReturns(succeeded) fakeResultSet.TotalReturns(failed + succeeded) - Expect(p.SummaryData()).To(Equal(Summary{ + Expect(p.SummaryData()).To(Equal(measurement.Summary{ Name: "foo measurement", Failed: 2, SummaryPhrase: "wingdang the foobrizzle", diff --git a/measurement/tcp_availability.go b/measurement/tcp_availability.go index 43c3010..8f58db0 100644 --- a/measurement/tcp_availability.go +++ b/measurement/tcp_availability.go @@ -25,11 +25,12 @@ func (t *tcpAvailability) SummaryPhrase() string { } func (t *tcpAvailability) PerformMeasurement() (string, string, string, bool) { - conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", t.url, t.port), 5*time.Second) + addr := net.JoinHostPort(t.url, fmt.Sprintf("%d", t.port)) + conn, err := net.DialTimeout("tcp", addr, 5*time.Second) if err != nil { return err.Error(), "", "", false } - defer conn.Close() + defer conn.Close() //nolint:errcheck _, err = conn.Write([]byte("knock-knock")) if err != nil { diff --git a/measurement/tcp_availability_test.go b/measurement/tcp_availability_test.go index f99452d..30620a3 100644 --- a/measurement/tcp_availability_test.go +++ b/measurement/tcp_availability_test.go @@ -3,7 +3,6 @@ package measurement_test import ( "fmt" "net" - "os" . "github.com/cloudfoundry/uptimer/measurement" @@ -17,8 +16,6 @@ var _ = Describe("TCPAvailability", func() { port int am BaseMeasurement - - listener net.Listener ) BeforeEach(func() { @@ -41,37 +38,42 @@ var _ = Describe("TCPAvailability", func() { }) Describe("PerformMeasurement", func() { + var ( + listener net.Listener + + done chan any + ) + Context("When the measurement client gets the expected response", func() { BeforeEach(func() { var err error listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", url, port)) - if err != nil { - fmt.Println("Error listening: ", err.Error()) - os.Exit(1) - } + Expect(err).NotTo(HaveOccurred()) + + done = make(chan any) // Listen for an incoming connection. go func() { + defer GinkgoRecover() + conn, err := listener.Accept() - if err != nil { - fmt.Println("Error accepting: ", err.Error()) - os.Exit(1) - } - // Handle connections in a new goroutine. - go func(conn net.Conn) { - defer conn.Close() - - _, err := conn.Write([]byte("Hello from Uptimer.")) - if err != nil { - fmt.Println("Error writing:", err.Error()) - os.Exit(1) - } - }(conn) + Expect(err).NotTo(HaveOccurred()) + + _, err = conn.Write([]byte("Hello from Uptimer.")) + Expect(err).NotTo(HaveOccurred()) + + err = conn.Close() + Expect(err).NotTo(HaveOccurred()) + + close(done) }() }) AfterEach(func() { - listener.Close() + err := listener.Close() + Expect(err).NotTo(HaveOccurred()) + + Eventually(done).Should(BeClosed()) }) It("records a matching string as success", func() { @@ -86,35 +88,36 @@ var _ = Describe("TCPAvailability", func() { BeforeEach(func() { var err error listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", url, port)) - if err != nil { - fmt.Println("Error listening: ", err.Error()) - os.Exit(1) - } + Expect(err).NotTo(HaveOccurred()) + + done = make(chan any) // Listen for an incoming connection. go func() { + defer GinkgoRecover() + conn, err := listener.Accept() - if err != nil { - fmt.Println("Error accepting: ", err.Error()) - os.Exit(1) - } - // Handle connections in a new goroutine. - go func(conn net.Conn) { - defer conn.Close() - - _, err := conn.Write([]byte("Hello from Zuptimer.")) - if err != nil { - fmt.Println("Error writing:", err.Error()) - os.Exit(1) - } - }(conn) + Expect(err).NotTo(HaveOccurred()) + + _, err = conn.Write([]byte("Hello from Zuptimer.")) + Expect(err).NotTo(HaveOccurred()) + + err = conn.Close() + Expect(err).NotTo(HaveOccurred()) + + close(done) }() }) + AfterEach(func() { + err := listener.Close() + Expect(err).NotTo(HaveOccurred()) + + Eventually(done).Should(BeClosed()) + }) + It("records a mismatched string as failure", func() { err, _, _, res := am.PerformMeasurement() - - listener.Close() Expect(err).To(Equal("TCP App not returning expected response")) Expect(res).To(BeFalse()) }) diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go index feb448b..d06a171 100644 --- a/orchestrator/orchestrator.go +++ b/orchestrator/orchestrator.go @@ -13,13 +13,14 @@ import ( "code.cloudfoundry.org/goshims/ioutilshim" + uuid "github.com/satori/go.uuid" + "github.com/cloudfoundry/uptimer/cfCmdGenerator" "github.com/cloudfoundry/uptimer/cfWorkflow" "github.com/cloudfoundry/uptimer/cmdRunner" "github.com/cloudfoundry/uptimer/cmdStartWaiter" "github.com/cloudfoundry/uptimer/config" "github.com/cloudfoundry/uptimer/measurement" - uuid "github.com/satori/go.uuid" ) //go:generate counterfeiter . Orchestrator diff --git a/orchestrator/orchestrator_test.go b/orchestrator/orchestrator_test.go index 6291d89..acd2bb2 100644 --- a/orchestrator/orchestrator_test.go +++ b/orchestrator/orchestrator_test.go @@ -16,11 +16,12 @@ import ( "github.com/cloudfoundry/uptimer/measurement/measurementfakes" . "github.com/cloudfoundry/uptimer/orchestrator" - "github.com/cloudfoundry/uptimer/cmdStartWaiter" - "github.com/cloudfoundry/uptimer/measurement" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/cloudfoundry/uptimer/cmdStartWaiter" + "github.com/cloudfoundry/uptimer/measurement" + "code.cloudfoundry.org/goshims/ioutilshim/ioutil_fake" )