package main

import (
	"bufio"
	"bytes"
	"io"
	"log"
	"math/rand"
	"os"
	"regexp"
	"testing"
	"time"
)

func TestReadAllOrWarnFail(t *testing.T) {
	rcv := captureLogs()
	defer uncaptureLogs()
	go func() {
		// The special file /proc/self/mem can be opened for
		// reading, but reading from byte 0 returns an error.
		f, err := os.Open("/proc/self/mem")
		if err != nil {
			t.Fatalf("Opening /proc/self/mem: %s", err)
		}
		if x, err := ReadAllOrWarn(f); err == nil {
			t.Fatalf("Expected error, got %v", x)
		}
	}()
	if msg, err := rcv.ReadBytes('\n'); err != nil {
		t.Fatal(err)
	} else if matched, err := regexp.MatchString("^crunchstat: .*error.*", string(msg)); err != nil || !matched {
		t.Fatalf("Expected error message about unreadable file, got \"%s\"", msg)
	}
}

func TestReadAllOrWarnSuccess(t *testing.T) {
	f, err := os.Open("./crunchstat_test.go")
	if err != nil {
		t.Fatalf("Opening ./crunchstat_test.go: %s", err)
	}
	data, err := ReadAllOrWarn(f)
	if err != nil {
		t.Fatalf("got error %s", err)
	}
	if matched, err := regexp.MatchString("^package main\n", string(data)); err != nil || !matched {
		t.Fatalf("data failed regexp: %s", err)
	}
}

// Test that CopyPipeToChildLog works even on lines longer than
// bufio.MaxScanTokenSize.
func TestCopyPipeToChildLogLongLines(t *testing.T) {
	rcv := captureLogs()
	defer uncaptureLogs()

	control := make(chan bool)
	pipeIn, pipeOut := io.Pipe()
	go CopyPipeToChildLog(pipeIn, control)

	sentBytes := make([]byte, bufio.MaxScanTokenSize+MaxLogLine+(1<<22))
	go func() {
		pipeOut.Write([]byte("before\n"))

		for i := range sentBytes {
			// Some bytes that aren't newlines:
			sentBytes[i] = byte((rand.Int() & 0xff) | 0x80)
		}
		sentBytes[len(sentBytes)-1] = '\n'
		pipeOut.Write(sentBytes)

		pipeOut.Write([]byte("after"))
		pipeOut.Close()
	}()

	if before, err := rcv.ReadBytes('\n'); err != nil || string(before) != "before\n" {
		t.Fatalf("\"before\n\" not received (got \"%s\", %s)", before, err)
	}

	var receivedBytes []byte
	done := false
	for !done {
		line, err := rcv.ReadBytes('\n')
		if err != nil {
			t.Fatal(err)
		}
		if len(line) >= 5 && string(line[0:5]) == "[...]" {
			if receivedBytes == nil {
				t.Fatal("Beginning of line reported as continuation")
			}
			line = line[5:]
		}
		if len(line) >= 6 && string(line[len(line)-6:len(line)]) == "[...]\n" {
			line = line[:len(line)-6]
		} else {
			done = true
		}
		receivedBytes = append(receivedBytes, line...)
	}
	if bytes.Compare(receivedBytes, sentBytes) != 0 {
		t.Fatalf("sent %d bytes, got %d different bytes", len(sentBytes), len(receivedBytes))
	}

	if after, err := rcv.ReadBytes('\n'); err != nil || string(after) != "after\n" {
		t.Fatal("\"after\n\" not received (got \"%s\", %s)", after, err)
	}

	select {
	case <-time.After(time.Second):
		t.Fatal("Timeout")
	case <-control:
		// Done.
	}
}

func captureLogs() *bufio.Reader {
	// Send childLog to our bufio reader instead of stderr
	stderrIn, stderrOut := io.Pipe()
	childLog = log.New(stderrOut, "", 0)
	statLog = log.New(stderrOut, "crunchstat: ", 0)
	return bufio.NewReader(stderrIn)
}

func uncaptureLogs() {
	childLog = log.New(os.Stderr, "", 0)
	statLog = log.New(os.Stderr, "crunchstat: ", 0)
}