// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package tasklifecycle

import (
	"testing"
	"time"

	"github.com/hashicorp/nomad/ci"
	"github.com/hashicorp/nomad/helper"
)

func TestGate(t *testing.T) {
	ci.Parallel(t)

	testCases := []struct {
		name string
		test func(*testing.T, *Gate)
	}{
		{
			name: "starts blocked",
			test: func(t *testing.T, g *Gate) {
				requireChannelBlocking(t, g.WaitCh(), "wait")
			},
		},
		{
			name: "block",
			test: func(t *testing.T, g *Gate) {
				g.Close()
				requireChannelBlocking(t, g.WaitCh(), "wait")
			},
		},
		{
			name: "allow",
			test: func(t *testing.T, g *Gate) {
				g.Open()
				requireChannelPassing(t, g.WaitCh(), "wait")
			},
		},
		{
			name: "block twice",
			test: func(t *testing.T, g *Gate) {
				g.Close()
				g.Close()
				requireChannelBlocking(t, g.WaitCh(), "wait")
			},
		},
		{
			name: "allow twice",
			test: func(t *testing.T, g *Gate) {
				g.Open()
				g.Open()
				requireChannelPassing(t, g.WaitCh(), "wait")
			},
		},
		{
			name: "allow block allow",
			test: func(t *testing.T, g *Gate) {
				g.Open()
				requireChannelPassing(t, g.WaitCh(), "first allow")
				g.Close()
				requireChannelBlocking(t, g.WaitCh(), "block")
				g.Open()
				requireChannelPassing(t, g.WaitCh(), "second allow")
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			shutdownCh := make(chan struct{})
			defer close(shutdownCh)

			g := NewGate(shutdownCh)
			tc.test(t, g)
		})
	}
}

// TestGate_shutdown tests a gate with a closed shutdown channel.
func TestGate_shutdown(t *testing.T) {
	ci.Parallel(t)

	// Create a Gate with a closed shutdownCh.
	shutdownCh := make(chan struct{})
	close(shutdownCh)

	g := NewGate(shutdownCh)

	// Test that Open() and Close() doesn't block forever.
	openCh := make(chan struct{})
	closeCh := make(chan struct{})

	go func() {
		g.Open()
		close(openCh)
	}()
	go func() {
		g.Close()
		close(closeCh)
	}()

	timer, stop := helper.NewSafeTimer(time.Second)
	defer stop()

	select {
	case <-openCh:
	case <-timer.C:
		t.Fatalf("timeout waiting for gate operations")
	}

	select {
	case <-closeCh:
	case <-timer.C:
		t.Fatalf("timeout waiting for gate operations")
	}

	// A Gate with a shutdownCh should be closed.
	requireChannelBlocking(t, g.WaitCh(), "gate should be closed")
}
