/*
Copyright (c) Edgeless Systems GmbH

SPDX-License-Identifier: AGPL-3.0-only
*/

package clean

import (
	"sync"
	"sync/atomic"
	"testing"

	"github.com/stretchr/testify/assert"
	"go.uber.org/goleak"
)

func TestMain(m *testing.M) {
	goleak.VerifyTestMain(m, goleak.IgnoreAnyFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1"))
}

func TestNew(t *testing.T) {
	assert := assert.New(t)

	cleaner := New(&spyStopper{stopped: &atomic.Bool{}})
	assert.NotNil(cleaner)
	assert.NotEmpty(cleaner.stoppers)
}

func TestWith(t *testing.T) {
	assert := assert.New(t)

	cleaner := New().With(&spyStopper{stopped: &atomic.Bool{}})
	assert.NotEmpty(cleaner.stoppers)
}

func TestClean(t *testing.T) {
	assert := assert.New(t)

	stopper := &spyStopper{stopped: &atomic.Bool{}}
	cleaner := New(stopper)
	go cleaner.Start()
	cleaner.Clean()
	cleaner.Done()
	assert.True(stopper.stopped.Load())
	// call again to make sure it doesn't panic or block or clean up again
	cleaner.Clean()
	assert.True(stopper.stopped.Load())
}

func TestCleanBeforeStart(t *testing.T) {
	assert := assert.New(t)
	// calling Clean before Start should work
	stopper := &spyStopper{stopped: &atomic.Bool{}}
	cleaner := New(stopper)
	cleaner.Clean()
	cleaner.Start()
	cleaner.Done()
	assert.True(stopper.stopped.Load())
}

func TestConcurrent(t *testing.T) {
	assert := assert.New(t)
	// calling Clean concurrently should call Stop exactly once

	stopper := &spyStopper{stopped: &atomic.Bool{}}
	cleaner := New(stopper)

	parallelism := 10
	wg := sync.WaitGroup{}

	start := func() {
		defer wg.Done()
		cleaner.Start()
	}

	clean := func() {
		defer wg.Done()
		cleaner.Clean()
	}

	done := func() {
		defer wg.Done()
		cleaner.Done()
	}

	wg.Add(3 * parallelism)
	for i := 0; i < parallelism; i++ {
		go start()
		go clean()
		go done()
	}
	wg.Wait()
	cleaner.Done()
	assert.True(stopper.stopped.Load())
}

type spyStopper struct {
	stopped *atomic.Bool
}

func (s *spyStopper) Stop() {
	s.stopped.Store(true)
}