// Copyright 2012 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 race_test

import (
	"runtime"
	"sync"
	"testing"
	"time"
)

func TestNoRaceWaitGroup(t *testing.T) {
	var x int
	var wg sync.WaitGroup
	n := 1
	for i := 0; i < n; i++ {
		wg.Add(1)
		j := i
		go func() {
			x = j
			wg.Done()
		}()
	}
	wg.Wait()
}

func TestRaceWaitGroup(t *testing.T) {
	var x int
	var wg sync.WaitGroup
	n := 2
	for i := 0; i < n; i++ {
		wg.Add(1)
		j := i
		go func() {
			x = j
			wg.Done()
		}()
	}
	wg.Wait()
}

func TestNoRaceWaitGroup2(t *testing.T) {
	var x int
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		x = 1
		wg.Done()
	}()
	wg.Wait()
	x = 2
}

// incrementing counter in Add and locking wg's mutex
func TestRaceWaitGroupAsMutex(t *testing.T) {
	var x int
	var wg sync.WaitGroup
	c := make(chan bool, 2)
	go func() {
		wg.Wait()
		time.Sleep(100 * time.Millisecond)
		wg.Add(+1)
		x = 1
		wg.Add(-1)
		c <- true
	}()
	go func() {
		wg.Wait()
		time.Sleep(100 * time.Millisecond)
		wg.Add(+1)
		x = 2
		wg.Add(-1)
		c <- true
	}()
	<-c
	<-c
}

// Incorrect usage: Add is too late.
func TestRaceWaitGroupWrongWait(t *testing.T) {
	c := make(chan bool, 2)
	var x int
	var wg sync.WaitGroup
	go func() {
		wg.Add(1)
		runtime.Gosched()
		x = 1
		wg.Done()
		c <- true
	}()
	go func() {
		wg.Add(1)
		runtime.Gosched()
		x = 2
		wg.Done()
		c <- true
	}()
	wg.Wait()
	<-c
	<-c
}

func TestRaceWaitGroupWrongAdd(t *testing.T) {
	c := make(chan bool, 2)
	var wg sync.WaitGroup
	go func() {
		wg.Add(1)
		time.Sleep(100 * time.Millisecond)
		wg.Done()
		c <- true
	}()
	go func() {
		wg.Add(1)
		time.Sleep(100 * time.Millisecond)
		wg.Done()
		c <- true
	}()
	time.Sleep(50 * time.Millisecond)
	wg.Wait()
	<-c
	<-c
}

func TestNoRaceWaitGroupMultipleWait(t *testing.T) {
	c := make(chan bool, 2)
	var wg sync.WaitGroup
	go func() {
		wg.Wait()
		c <- true
	}()
	go func() {
		wg.Wait()
		c <- true
	}()
	wg.Wait()
	<-c
	<-c
}

func TestNoRaceWaitGroupMultipleWait2(t *testing.T) {
	c := make(chan bool, 2)
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		wg.Done()
		wg.Wait()
		c <- true
	}()
	go func() {
		wg.Done()
		wg.Wait()
		c <- true
	}()
	wg.Wait()
	<-c
	<-c
}

func TestNoRaceWaitGroupMultipleWait3(t *testing.T) {
	const P = 3
	var data [P]int
	done := make(chan bool, P)
	var wg sync.WaitGroup
	wg.Add(P)
	for p := 0; p < P; p++ {
		go func(p int) {
			data[p] = 42
			wg.Done()
		}(p)
	}
	for p := 0; p < P; p++ {
		go func() {
			wg.Wait()
			for p1 := 0; p1 < P; p1++ {
				_ = data[p1]
			}
			done <- true
		}()
	}
	for p := 0; p < P; p++ {
		<-done
	}
}

// Correct usage but still a race
func TestRaceWaitGroup2(t *testing.T) {
	var x int
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		x = 1
		wg.Done()
	}()
	go func() {
		x = 2
		wg.Done()
	}()
	wg.Wait()
}

func TestNoRaceWaitGroupPanicRecover(t *testing.T) {
	var x int
	var wg sync.WaitGroup
	defer func() {
		err := recover()
		if err != "sync: negative WaitGroup counter" {
			t.Fatalf("Unexpected panic: %#v", err)
		}
		x = 2
	}()
	x = 1
	wg.Add(-1)
}

// TODO: this is actually a panic-synchronization test, not a
// WaitGroup test. Move it to another *_test file
// Is it possible to get a race by synchronization via panic?
func TestNoRaceWaitGroupPanicRecover2(t *testing.T) {
	var x int
	var wg sync.WaitGroup
	ch := make(chan bool, 1)
	var f func() = func() {
		x = 2
		ch <- true
	}
	go func() {
		defer func() {
			err := recover()
			if err != "sync: negative WaitGroup counter" {
			}
			go f()
		}()
		x = 1
		wg.Add(-1)
	}()

	<-ch
}

func TestNoRaceWaitGroupTransitive(t *testing.T) {
	x, y := 0, 0
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		x = 42
		wg.Done()
	}()
	go func() {
		time.Sleep(1e7)
		y = 42
		wg.Done()
	}()
	wg.Wait()
	_ = x
	_ = y
}

func TestNoRaceWaitGroupReuse(t *testing.T) {
	const P = 3
	var data [P]int
	var wg sync.WaitGroup
	for try := 0; try < 3; try++ {
		wg.Add(P)
		for p := 0; p < P; p++ {
			go func(p int) {
				data[p]++
				wg.Done()
			}(p)
		}
		wg.Wait()
		for p := 0; p < P; p++ {
			data[p]++
		}
	}
}

func TestNoRaceWaitGroupReuse2(t *testing.T) {
	const P = 3
	var data [P]int
	var wg sync.WaitGroup
	for try := 0; try < 3; try++ {
		wg.Add(P)
		for p := 0; p < P; p++ {
			go func(p int) {
				data[p]++
				wg.Done()
			}(p)
		}
		done := make(chan bool)
		go func() {
			wg.Wait()
			for p := 0; p < P; p++ {
				data[p]++
			}
			done <- true
		}()
		wg.Wait()
		<-done
		for p := 0; p < P; p++ {
			data[p]++
		}
	}
}

func TestRaceWaitGroupReuse(t *testing.T) {
	const P = 3
	const T = 3
	done := make(chan bool, T)
	var wg sync.WaitGroup
	for try := 0; try < T; try++ {
		var data [P]int
		wg.Add(P)
		for p := 0; p < P; p++ {
			go func(p int) {
				time.Sleep(50 * time.Millisecond)
				data[p]++
				wg.Done()
			}(p)
		}
		go func() {
			wg.Wait()
			for p := 0; p < P; p++ {
				data[p]++
			}
			done <- true
		}()
		time.Sleep(100 * time.Millisecond)
		wg.Wait()
	}
	for try := 0; try < T; try++ {
		<-done
	}
}

func TestNoRaceWaitGroupConcurrentAdd(t *testing.T) {
	const P = 4
	waiting := make(chan bool, P)
	var wg sync.WaitGroup
	for p := 0; p < P; p++ {
		go func() {
			wg.Add(1)
			waiting <- true
			wg.Done()
		}()
	}
	for p := 0; p < P; p++ {
		<-waiting
	}
	wg.Wait()
}
