// Copyright 2022 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"
)

// We assume that the Once.Do tests have already covered parallelism.

func TestOnceFunc(t *testing.T) {
	calls := 0
	f := sync.OnceFunc(func() { calls++ })
	allocs := testing.AllocsPerRun(10, f)
	if calls != 1 {
		t.Errorf("want calls==1, got %d", calls)
	}
	if allocs != 0 {
		t.Errorf("want 0 allocations per call, got %v", allocs)
	}
}

func TestOnceValue(t *testing.T) {
	calls := 0
	f := sync.OnceValue(func() int {
		calls++
		return calls
	})
	allocs := testing.AllocsPerRun(10, func() { f() })
	value := f()
	if calls != 1 {
		t.Errorf("want calls==1, got %d", calls)
	}
	if value != 1 {
		t.Errorf("want value==1, got %d", value)
	}
	if allocs != 0 {
		t.Errorf("want 0 allocations per call, got %v", allocs)
	}
}

func TestOnceValues(t *testing.T) {
	calls := 0
	f := sync.OnceValues(func() (int, int) {
		calls++
		return calls, calls + 1
	})
	allocs := testing.AllocsPerRun(10, func() { f() })
	v1, v2 := f()
	if calls != 1 {
		t.Errorf("want calls==1, got %d", calls)
	}
	if v1 != 1 || v2 != 2 {
		t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2)
	}
	if allocs != 0 {
		t.Errorf("want 0 allocations per call, got %v", allocs)
	}
}

// TODO: need to implement more complete panic handling for these tests.
// func testOncePanicX(t *testing.T, calls *int, f func()) {
// 	testOncePanicWith(t, calls, f, func(label string, p any) {
// 		if p != "x" {
// 			t.Fatalf("%s: want panic %v, got %v", label, "x", p)
// 		}
// 	})
// }

// func testOncePanicWith(t *testing.T, calls *int, f func(), check func(label string, p any)) {
// 	// Check that the each call to f panics with the same value, but the
// 	// underlying function is only called once.
// 	for _, label := range []string{"first time", "second time"} {
// 		var p any
// 		panicked := true
// 		func() {
// 			defer func() {
// 				p = recover()
// 			}()
// 			f()
// 			panicked = false
// 		}()
// 		if !panicked {
// 			t.Fatalf("%s: f did not panic", label)
// 		}
// 		check(label, p)
// 	}
// 	if *calls != 1 {
// 		t.Errorf("want calls==1, got %d", *calls)
// 	}
// }

// func TestOnceFuncPanic(t *testing.T) {
// 	calls := 0
// 	f := sync.OnceFunc(func() {
// 		calls++
// 		panic("x")
// 	})
// 	testOncePanicX(t, &calls, f)
// }

// func TestOnceValuePanic(t *testing.T) {
// 	calls := 0
// 	f := sync.OnceValue(func() int {
// 		calls++
// 		panic("x")
// 	})
// 	testOncePanicX(t, &calls, func() { f() })
// }

// func TestOnceValuesPanic(t *testing.T) {
// 	calls := 0
// 	f := sync.OnceValues(func() (int, int) {
// 		calls++
// 		panic("x")
// 	})
// 	testOncePanicX(t, &calls, func() { f() })
// }
//
// func TestOnceFuncPanicNil(t *testing.T) {
// 	calls := 0
// 	f := sync.OnceFunc(func() {
// 		calls++
// 		panic(nil)
// 	})
// 	testOncePanicWith(t, &calls, f, func(label string, p any) {
// 		switch p.(type) {
// 		case nil, *runtime.PanicNilError:
// 			return
// 		}
// 		t.Fatalf("%s: want nil panic, got %v", label, p)
// 	})
// }
//
// func TestOnceFuncGoexit(t *testing.T) {
// 	// If f calls Goexit, the results are unspecified. But check that f doesn't
// 	// get called twice.
// 	calls := 0
// 	f := sync.OnceFunc(func() {
// 		calls++
// 		runtime.Goexit()
// 	})
// 	var wg sync.WaitGroup
// 	for i := 0; i < 2; i++ {
// 		wg.Add(1)
// 		go func() {
// 			defer wg.Done()
// 			defer func() { recover() }()
// 			f()
// 		}()
// 		wg.Wait()
// 	}
// 	if calls != 1 {
// 		t.Errorf("want calls==1, got %d", calls)
// 	}
// }
