/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package io

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"syscall"
	"testing"
)

func writeToPipe(namedPipe string, flaky bool, i int) {
	pipe, err := os.OpenFile(namedPipe, os.O_WRONLY, 0600)
	if err != nil {
		return
	}

	// The first two reads should never be consistent but all
	// subsequent reads should be
	outstr := fmt.Sprintf("Foobar %t", (i <= 0))

	if flaky {
		outstr = fmt.Sprintf("Foobar %d", i)
	}

	pipe.Write([]byte(outstr))
	pipe.Close()
}

func makePipe(t *testing.T) string {
	tmp, err := ioutil.TempDir("", "pipe-test")
	if err != nil {
		t.Fatal(err)
	}

	pipe := filepath.Join(tmp, "pipe")
	syscall.Mkfifo(pipe, 0600)

	return pipe
}

func writer(namedPipe string, flaky bool, c <-chan int, d <-chan bool) {
	// Make sure something is in the fifo otherwise the first iteration of
	// ConsistentRead will block forever
	writeToPipe(namedPipe, flaky, -1)

	for {
		select {
		case i := <-c:
			writeToPipe(namedPipe, flaky, i)
		case <-d:
			os.RemoveAll(namedPipe)
			return
		}
	}
}

func TestConsistentRead(t *testing.T) {
	pipe := makePipe(t)
	prog, done := make(chan int), make(chan bool)
	go writer(pipe, false, prog, done)

	if _, err := consistentReadSync(pipe, 3, func(i int) { prog <- i }); err != nil {
		t.Fatal(err)
	}

	done <- true
}

func TestConsistentReadFlakyReader(t *testing.T) {
	pipe := makePipe(t)
	prog, done := make(chan int), make(chan bool)
	go writer(pipe, true, prog, done)

	var err error
	if _, err = consistentReadSync(pipe, 3, func(i int) { prog <- i }); err == nil {
		t.Fatal("flaky reader returned consistent results")
	}
	if !IsInconsistentReadError(err) {
		t.Errorf("Unexpected error returned, expected InconsistentReadError, got: %T / %q", err, err)
	}
}

func TestReadAtMost(t *testing.T) {
	testCases := []struct {
		limit  int64
		data   string
		errMsg string
	}{
		{4, "hell", "the read limit is reached"},
		{5, "hello", "the read limit is reached"},
		{6, "hello", ""},
	}

	for _, tc := range testCases {
		r := strings.NewReader("hello")
		data, err := ReadAtMost(r, tc.limit)
		if string(data) != tc.data {
			t.Errorf("Read limit %d: expected \"%s\", got \"%s\"", tc.limit, tc.data, string(data))
		}

		if err == nil && tc.errMsg != "" {
			t.Errorf("Read limit %d: expected error with message \"%s\", got no error", tc.limit, tc.errMsg)
		}

		if err != nil && err.Error() != tc.errMsg {
			t.Errorf("Read limit %d: expected error with message \"%s\", got error with message \"%s\"", tc.limit, tc.errMsg, err.Error())
		}
	}
}
