// Copyright 2010 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 sysrand

import (
	"bytes"
	"compress/flate"
	"internal/testenv"
	"os"
	"runtime"
	"sync"
	"testing"
)

func TestRead(t *testing.T) {
	// 40MiB, more than the documented maximum of 32Mi-1 on Linux 32-bit.
	b := make([]byte, 40<<20)
	Read(b)

	if testing.Short() {
		b = b[len(b)-100_000:]
	}

	var z bytes.Buffer
	f, _ := flate.NewWriter(&z, 5)
	f.Write(b)
	f.Close()
	if z.Len() < len(b)*99/100 {
		t.Fatalf("Compressed %d -> %d", len(b), z.Len())
	}
}

func TestReadByteValues(t *testing.T) {
	b := make([]byte, 1)
	v := make(map[byte]bool)
	for {
		Read(b)
		v[b[0]] = true
		if len(v) == 256 {
			break
		}
	}
}

func TestReadEmpty(t *testing.T) {
	Read(make([]byte, 0))
	Read(nil)
}

func TestConcurrentRead(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping in short mode")
	}
	const N = 100
	const M = 1000
	var wg sync.WaitGroup
	wg.Add(N)
	for i := 0; i < N; i++ {
		go func() {
			defer wg.Done()
			for i := 0; i < M; i++ {
				b := make([]byte, 32)
				Read(b)
			}
		}()
	}
	wg.Wait()
}

// TestNoUrandomFallback ensures the urandom fallback is not reached in
// normal operations.
func TestNoUrandomFallback(t *testing.T) {
	expectFallback := false
	if runtime.GOOS == "aix" {
		// AIX always uses the urandom fallback.
		expectFallback = true
	}
	if os.Getenv("GO_GETRANDOM_DISABLED") == "1" {
		// We are testing the urandom fallback intentionally.
		expectFallback = true
	}
	Read(make([]byte, 1))
	if urandomFile != nil && !expectFallback {
		t.Error("/dev/urandom fallback used unexpectedly")
		t.Log("note: if this test fails, it may be because the system does not have getrandom(2)")
	}
	if urandomFile == nil && expectFallback {
		t.Error("/dev/urandom fallback not used as expected")
	}
}

func TestReadError(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode")
	}
	testenv.MustHaveExec(t)

	// We run this test in a subprocess because it's expected to crash.
	if os.Getenv("GO_TEST_READ_ERROR") == "1" {
		testingOnlyFailRead = true
		Read(make([]byte, 32))
		t.Error("Read did not crash")
		return
	}

	cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestReadError$")
	cmd.Env = append(os.Environ(), "GO_TEST_READ_ERROR=1")
	out, err := cmd.CombinedOutput()
	if err == nil {
		t.Error("subprocess succeeded unexpectedly")
	}
	exp := "fatal error: crypto/rand: failed to read random data"
	if !bytes.Contains(out, []byte(exp)) {
		t.Errorf("subprocess output does not contain %q: %s", exp, out)
	}
}
