// Tests run for (almost all) combinations of openssl, aessiv, plaintextnames.
//
// File reading, writing, modification, truncate, ...
//
// Runs all tests N times, for the combinations of different flags specified
// in the `matrix` variable.

package matrix

import (
	"flag"
	"fmt"
	"os"
	"testing"
	"time"

	"github.com/rfjakob/gocryptfs/v2/internal/stupidgcm"
	"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
)

// Several tests need to be aware if plaintextnames is active or not, so make this
// a global variable
var testcase testcaseMatrix

var ctlsockPath string

type testcaseMatrix struct {
	plaintextnames bool
	openssl        string
	aessiv         bool
	raw64          bool
	extraArgs      []string
}

// isSet finds out if `extraArg` is set in `tc.extraArgs`
func (tc *testcaseMatrix) isSet(extraArg string) bool {
	for _, v := range tc.extraArgs {
		if v == extraArg {
			return true
		}
	}
	return false
}

// This is the entry point for the tests
func TestMain(m *testing.M) {
	var matrix = []testcaseMatrix{
		// Normal
		{false, "auto", false, false, nil},
		{false, "true", false, false, nil},
		{false, "false", false, false, nil},
		// Plaintextnames
		{true, "true", false, false, nil},
		{true, "false", false, false, nil},
		// AES-SIV (does not use openssl, no need to test permutations)
		{false, "auto", true, false, nil},
		{true, "auto", true, false, nil},
		// Raw64
		{false, "auto", false, true, nil},
		// -serialize_reads
		{false, "auto", false, false, []string{"-serialize_reads"}},
		{false, "auto", false, false, []string{"-sharedstorage"}},
		{false, "auto", false, false, []string{"-deterministic-names"}},
		// Test xchacha with and without openssl
		{false, "true", false, true, []string{"-xchacha"}},
		{false, "false", false, true, []string{"-xchacha"}},
	}

	// Make "testing.Verbose()" return the correct value
	flag.Parse()
	var i int
	for i, testcase = range matrix {
		if testcase.openssl == "true" && stupidgcm.BuiltWithoutOpenssl {
			continue
		}
		if testing.Verbose() {
			fmt.Printf("matrix: testcase = %#v\n", testcase)
		}
		createDirIV := true
		if testcase.plaintextnames {
			createDirIV = false
		} else if testcase.isSet("-deterministic-names") {
			createDirIV = false
		}
		ctlsockPath = fmt.Sprintf("%s/ctlsock.%d", test_helpers.TmpDir, i)
		test_helpers.ResetTmpDir(createDirIV)
		opts := []string{"-zerokey", "-ctlsock", ctlsockPath}
		//opts = append(opts, "-fusedebug")
		opts = append(opts, fmt.Sprintf("-openssl=%v", testcase.openssl))
		opts = append(opts, fmt.Sprintf("-plaintextnames=%v", testcase.plaintextnames))
		opts = append(opts, fmt.Sprintf("-aessiv=%v", testcase.aessiv))
		opts = append(opts, fmt.Sprintf("-raw64=%v", testcase.raw64))
		opts = append(opts, testcase.extraArgs...)
		test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, opts...)
		before := test_helpers.ListFds(0, test_helpers.TmpDir)
		t0 := time.Now()
		r := m.Run()
		if testing.Verbose() {
			fmt.Printf("matrix[%d]: run took %v\n", i, time.Since(t0))
		}
		// Catch fd leaks in the tests. NOTE: this does NOT catch leaks in
		// the gocryptfs FUSE process, but only in the tests that access it!
		// All fds that point outside TmpDir are not interesting (the Go test
		// infrastucture creates temporary log files we don't care about).
		after := test_helpers.ListFds(0, test_helpers.TmpDir)
		if len(before) != len(after) {
			fmt.Printf("fd leak in test process? before, after:\n%v\n%v\n", before, after)
			os.Exit(1)
		}
		test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
		if r != 0 {
			fmt.Printf("TestMain: matrix[%d] = %#v failed\n", i, testcase)
			os.Exit(r)
		}
		// The ctlsock file is deleted asynchronously after unmount.
		// Ensure it is delete here so it does not race (and trip up) ResetTmpDir() of the next
		// loop iteration.
		os.Remove(ctlsockPath)
	}
	os.Exit(0)
}
