<commit_before>// sync_test.go
//
// Copyright (c) 2016, <NAME>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package sync

import (
	"io/ioutil"
	"testing"

	"github.com/aykevl/dtsync/tree"
	"github.com/aykevl/dtsync/tree/memory"
)

type testCase struct {
	file      string
	action    Action
	contents  []byte
	fileCount int64
}

func TestSync(t *testing.T) {
	fs1 := memory.NewRoot()
	fs2 := memory.NewRoot()

	result, err := Sync(fs1, fs2)
	if err != nil {
		t.Fatal("could not start sync:", err)
	}
	result.MarkFullySynced()
	err = result.SaveStatus()
	if err != nil {
		t.Error("could not save replica state:", err)
	}
	if fs1.Size() != 1 {
		t.Error("replica state wasn't saved for fs1")
	}
	if fs2.Size() != 1 {
		t.Error("replica state wasn't saved for fs2")
	}

	testCases := []testCase{
		{"file1.txt", ACTION_COPY, []byte("The quick brown fox..."), 2},
		{"file1.txt", ACTION_UPDATE, []byte("The quick brown fox jumps over the lazy dog."), 2},
		{"file1.txt", ACTION_REMOVE, nil, 1},
	}

	runTests(t, fs1, fs2, false, testCases)
	runTests(t, fs1, fs2, true, testCases)
	runTests(t, fs2, fs1, false, testCases)
	runTests(t, fs2, fs1, true, testCases)
}

func runTests(t *testing.T, fs1, fs2 *memory.Entry, swap bool, cases []testCase) {
	for _, tc := range cases {
		statusBefore := readStatuses(t, fs1, fs2)

		var err error
		switch tc.action {
		case ACTION_COPY: // add
			_, err = fs1.AddRegular(tc.file, tc.contents)
		case ACTION_UPDATE:
			child := getFile(fs1, tc.file)
			if child == nil {
				t.Fatalf("could not find file %s to update", tc.file)
			}
			child.SetContents(tc.contents)
		case ACTION_REMOVE:
			child := getFile(fs1, tc.file)
			if child == nil {
				t.Fatalf("could not find file %s to remove", tc.file)
			}
			err = fs1.Remove(child)
		default:
			t.Fatalf("unknown action: %d", tc.action)
		}
		if err != nil {
			t.Fatalf("could not %s file %s: %s", tc.action, tc.file, err)
		}

		if swap {
			runTestCase(t, &tc, fs2, fs1)
		} else {
			runTestCase(t, &tc, fs1, fs2)
		}

		if t.Failed() {
			t.Logf("Action: %s %s", tc.action, tc.file)
			t.Logf("Status before, side 1\n%s", string(statusBefore[0]))
			t.Logf("Status before, side 2\n%s", string(statusBefore[1]))
			statusAfter := readStatuses(t, fs1, fs2)
			t.Logf("Status after, side 1\n%s", string(statusAfter[0]))
			t.Logf("Status after, side 2\n%s", string(statusAfter[1]))
		}
		if t.Failed() {
			t.FailNow()
		}
	}
}

func runTestCase(t *testing.T, tc *testCase, fs1, fs2 *memory.Entry) {
	result, err := Sync(fs1, fs2)
	if err != nil {
		t.Errorf("could not sync after: %s %s: %s", tc.action, tc.file, err)
	} else {
		if err := result.SyncAll(); err != nil {
			t.Errorf("could not sync all: %s", err)
		}
		if !fsEqual(fs1, fs2) {
			t.Errorf("directory trees are not equal after: %s %s", tc.action, tc.file)
		}
		result.MarkFullySynced()
		if err := result.SaveStatus(); err != nil {
			t.Errorf("could not save status: %s", err)
		}

		if fs1.Size() != tc.fileCount || fs2.Size() != tc.fileCount {
			t.Errorf("unexpected number of files after first sync (expected %d): fs1=%d fs2=%d", tc.fileCount, fs1.Size(), fs2.Size())
		}
	}<commit_msg>improve runTestCase logic (e.g. return early on errors)<commit_after>// sync_test.go
//
// Copyright (c) 2016, <NAME>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package sync

import (
	"io/ioutil"
	"testing"

	"github.com/aykevl/dtsync/tree"
	"github.com/aykevl/dtsync/tree/memory"
)

type testCase struct {
	file      string
	action    Action
	contents  []byte
	fileCount int64
}

func TestSync(t *testing.T) {
	fs1 := memory.NewRoot()
	fs2 := memory.NewRoot()

	result, err := Sync(fs1, fs2)
	if err != nil {
		t.Fatal("could not start sync:", err)
	}
	result.MarkFullySynced()
	err = result.SaveStatus()
	if err != nil {
		t.Error("could not save replica state:", err)
	}
	if fs1.Size() != 1 {
		t.Error("replica state wasn't saved for fs1")
	}
	if fs2.Size() != 1 {
		t.Error("replica state wasn't saved for fs2")
	}

	testCases := []testCase{
		{"file1.txt", ACTION_COPY, []byte("The quick brown fox..."), 2},
		{"file1.txt", ACTION_UPDATE, []byte("The quick brown fox jumps over the lazy dog."), 2},
		{"file1.txt", ACTION_REMOVE, nil, 1},
	}

	runTests(t, fs1, fs2, false, testCases)
	runTests(t, fs1, fs2, true, testCases)
	runTests(t, fs2, fs1, false, testCases)
	runTests(t, fs2, fs1, true, testCases)
}

func runTests(t *testing.T, fs1, fs2 *memory.Entry, swap bool, cases []testCase) {
	for _, tc := range cases {
		statusBefore := readStatuses(t, fs1, fs2)

		var err error
		switch tc.action {
		case ACTION_COPY: // add
			_, err = fs1.AddRegular(tc.file, tc.contents)
		case ACTION_UPDATE:
			child := getFile(fs1, tc.file)
			if child == nil {
				t.Fatalf("could not find file %s to update", tc.file)
			}
			child.SetContents(tc.contents)
		case ACTION_REMOVE:
			child := getFile(fs1, tc.file)
			if child == nil {
				t.Fatalf("could not find file %s to remove", tc.file)
			}
			err = fs1.Remove(child)
		default:
			t.Fatalf("unknown action: %d", tc.action)
		}
		if err != nil {
			t.Fatalf("could not %s file %s: %s", tc.action, tc.file, err)
		}

		if swap {
			runTestCase(t, &tc, fs2, fs1)
		} else {
			runTestCase(t, &tc, fs1, fs2)
		}

		if t.Failed() {
			t.Logf("Action: %s %s", tc.action, tc.file)
			t.Logf("Status before, side 1\n%s", string(statusBefore[0]))
			t.Logf("Status before, side 2\n%s", string(statusBefore[1]))
			statusAfter := readStatuses(t, fs1, fs2)
			t.Logf("Status after, side 1\n%s", string(statusAfter[0]))
			t.Logf("Status after, side 2\n%s", string(statusAfter[1]))
		}
		if t.Failed() {
			t.FailNow()
		}
	}
}

func runTestCase(t *testing.T, tc *testCase, fs1, fs2 *memory.Entry) {
	result, err := Sync(fs1, fs2)
	if err != nil {
		t.Errorf("could not sync after: %s %s: %s", tc.action, tc.file, err)
		return
	}
	if err := result.SyncAll(); err != nil {
		t.Errorf("could not sync all: %s", err)
		return
	}
	if !fsEqual(fs1, fs2) {
		t.Errorf("directory trees are not equal after: %s %s", tc.action, tc.file)
	} else {
		result.MarkFullySynced()
		if err := result.SaveStatus(); err != nil {
			t.Errorf("could not save status: %s", err)
		}
	}

	if fs1.Size() != tc.fileCount || fs2.Size() != tc.fileCount {
		t.Errorf("unexpected number of files after first sync (expected %d): fs1=%d fs2=%d", tc.fileCount, fs1.Size(), fs2.Size())
	}
{'fraction_non_alphanumeric': 0.13916545474885333, 'fraction_numerical': 0.014766752433158071, 'mean_word_length': 3.554253693326541, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '65025', 'n_tokens_mistral': 3387, 'n_tokens_neox': 3097, 'n_words': 1035}