1
0
Fork 0

Adding upstream version 3.10.8.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-18 09:37:23 +02:00
parent 37e9b6d587
commit 03bfe4079e
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
356 changed files with 28857 additions and 0 deletions

397
tree/tests/f3/creator.go Normal file
View file

@ -0,0 +1,397 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package f3
import (
"context"
"crypto/sha256"
"fmt"
"io"
"strings"
"time"
"code.forgejo.org/f3/gof3/v3/f3"
helpers_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/repository"
tests_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/tests/repository"
"code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/logger"
"code.forgejo.org/f3/gof3/v3/path"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"github.com/stretchr/testify/require"
)
var RootUser *f3.User = &f3.User{
UserName: "root",
}
type Creator struct {
logger logger.Interface
t TestingT
d string
name string
serial int
}
func now() time.Time {
return time.Now().Truncate(time.Second)
}
func tick(now *time.Time) time.Time {
*now = now.Add(1 * time.Minute)
return *now
}
func NewCreator(t TestingT, name string, logger logger.Interface) *Creator {
return &Creator{
t: t,
d: t.TempDir(),
logger: logger,
name: name,
}
}
func (f *Creator) randomString(prefix string) string {
f.serial++
return fmt.Sprintf("%s%s%015d", prefix, f.name, f.serial)
}
func (f *Creator) GetDirectory() string {
return f.d
}
func (f *Creator) Generate(k kind.Kind, parent path.Path) f3.Interface {
switch k {
case f3_tree.KindForge:
return f.GenerateForge()
case f3_tree.KindUser:
return f.GenerateUser()
case f3_tree.KindOrganization:
return f.GenerateOrganization()
case f3_tree.KindProject:
return f.GenerateProject()
case f3_tree.KindIssue:
return f.GenerateIssue(parent)
case f3_tree.KindMilestone:
return f.GenerateMilestone()
case f3_tree.KindTopic:
return f.GenerateTopic()
case f3_tree.KindReaction:
return f.GenerateReaction(parent)
case f3_tree.KindLabel:
return f.GenerateLabel()
case f3_tree.KindComment:
return f.GenerateComment(parent)
case f3_tree.KindRepository:
return f.GenerateRepository(f3.RepositoryNameDefault)
case f3_tree.KindRelease:
return f.GenerateRelease(parent)
case f3_tree.KindAsset:
return f.GenerateAsset(parent)
case f3_tree.KindPullRequest:
return f.GeneratePullRequest(parent)
case f3_tree.KindReview:
return f.GenerateReview(parent)
case f3_tree.KindReviewComment:
return f.GenerateReviewComment(parent)
default:
panic(fmt.Errorf("not implemented %s", k))
}
}
func (f *Creator) GenerateForge() *f3.Forge {
return &f3.Forge{
Common: f3.NewCommon("forge"),
}
}
func (f *Creator) GenerateUser() *f3.User {
username := f.randomString("user")
return &f3.User{
Name: username + " Doe",
UserName: username,
Email: username + "@example.com",
Password: "Wrobyak4",
}
}
func (f *Creator) GenerateOrganization() *f3.Organization {
orgname := f.randomString("org")
return &f3.Organization{
FullName: orgname + " Lambda",
Name: orgname,
}
}
func (f *Creator) GenerateProject() *f3.Project {
projectname := f.randomString("project")
return &f3.Project{
Name: projectname,
IsPrivate: false,
IsMirror: false,
Description: "project description",
DefaultBranch: "master",
}
}
func (f *Creator) GenerateForkedProject(parent path.Path, forked string) *f3.Project {
project := f.GenerateProject()
project.Forked = f3.NewReference(forked)
return project
}
func (f *Creator) GenerateRepository(name string) *f3.Repository {
repository := &f3.Repository{
Name: name,
}
p := f.t.TempDir()
helper := tests_repository.NewTestHelper(f.t, p, nil)
helper.CreateRepositoryContent("").PushMirror()
repository.FetchFunc = func(ctx context.Context, destination string, internalRefs []string) {
f.logger.Debug("%s %s", p, destination)
helpers_repository.GitMirror(context.Background(), nil, p, destination, internalRefs)
}
return repository
}
func (f *Creator) GenerateReaction(parent path.Path) *f3.Reaction {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
return &f3.Reaction{
UserID: f3_tree.NewUserReference(user.GetID()),
Content: "heart",
}
}
func (f *Creator) GenerateMilestone() *f3.Milestone {
now := now()
created := tick(&now)
updated := tick(&now)
deadline := tick(&now)
return &f3.Milestone{
Title: "milestone1",
Description: "milestone1 description",
Deadline: &deadline,
Created: created,
Updated: &updated,
Closed: nil,
State: f3.MilestoneStateOpen,
}
}
func (f *Creator) GeneratePullRequest(parent path.Path) *f3.PullRequest {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs"))
repositoryNode.Get(context.Background())
repositoryHelper := tests_repository.NewTestHelper(f.t, "", repositoryNode)
mainRef := "master"
mainSha := repositoryHelper.GetRepositorySha(mainRef)
featureRef := "feature"
repositoryHelper.InternalBranchRepositoryFeature(featureRef, "feature content")
featureSha := repositoryHelper.GetRepositorySha(featureRef)
f.logger.Debug("master %s at main %s feature %s", repositoryHelper.GetBare(), mainSha, featureSha)
repositoryHelper.PushMirror()
now := now()
prCreated := tick(&now)
prUpdated := tick(&now)
return &f3.PullRequest{
PosterID: f3_tree.NewUserReference(user.GetID()),
Title: "pr title",
Content: "pr content",
State: f3.PullRequestStateOpen,
IsLocked: false,
Created: prCreated,
Updated: prUpdated,
Closed: nil,
Merged: false,
MergedTime: nil,
MergeCommitSHA: "",
Head: f3.PullRequestBranch{
Ref: featureRef,
SHA: featureSha,
Repository: f3_tree.NewPullRequestSameRepositoryReference(),
},
Base: f3.PullRequestBranch{
Ref: mainRef,
SHA: mainSha,
Repository: f3_tree.NewPullRequestSameRepositoryReference(),
},
}
}
func (f *Creator) GenerateLabel() *f3.Label {
name := f.randomString("label")
return &f3.Label{
Name: name,
Color: "ffffff",
Description: name + " description",
}
}
func (f *Creator) GenerateTopic() *f3.Topic {
return &f3.Topic{
Name: "topic1",
}
}
func (f *Creator) GenerateIssue(parent path.Path) *f3.Issue {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
labelsNode := projectNode.Find(generic.NewPathFromString("labels"))
require.NotEqualValues(f.t, generic.NilNode, labelsNode)
labels := labelsNode.GetChildren()
require.NotEmpty(f.t, labels)
firstLabel := labels[0]
milestonesNode := projectNode.Find(generic.NewPathFromString("milestones"))
require.NotEqualValues(f.t, generic.NilNode, milestonesNode)
milestones := milestonesNode.GetChildren()
require.NotEmpty(f.t, milestones)
firstMilestone := milestones[0]
now := now()
updated := tick(&now)
closed := tick(&now)
created := tick(&now)
return &f3.Issue{
PosterID: f3_tree.NewUserReference(user.GetID()),
Assignees: []*f3.Reference{f3_tree.NewUserReference(user.GetID())},
Labels: []*f3.Reference{f3_tree.NewIssueLabelReference(firstLabel.GetID())},
Milestone: f3_tree.NewIssueMilestoneReference(firstMilestone.GetID()),
Title: "title",
Content: "content",
State: f3.IssueStateOpen,
IsLocked: false,
Created: created,
Updated: updated,
Closed: &closed,
}
}
func (f *Creator) GenerateComment(parent path.Path) *f3.Comment {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
now := now()
commentCreated := tick(&now)
commentUpdated := tick(&now)
posterID := f3_tree.NewUserReference(user.GetID())
return &f3.Comment{
PosterID: posterID,
Created: commentCreated,
Updated: commentUpdated,
Content: "comment content",
}
}
func (f *Creator) GenerateRelease(parent path.Path) *f3.Release {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
project := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
repository := project.Find(generic.NewPathFromString("repositories/vcs"))
repository.Get(context.Background())
repositoryHelper := tests_repository.NewTestHelper(f.t, "", repository)
now := now()
releaseCreated := tick(&now)
tag := "releasetagv12"
repositoryHelper.CreateRepositoryTag(tag, "master")
sha := repositoryHelper.GetRepositorySha("master")
repositoryHelper.PushMirror()
return &f3.Release{
TagName: tag,
TargetCommitish: sha,
Name: "v12 name",
Body: "v12 body",
Draft: false,
Prerelease: false,
PublisherID: f3_tree.NewUserReference(user.GetID()),
Created: releaseCreated,
}
}
func (f *Creator) GenerateAsset(parent path.Path) *f3.ReleaseAsset {
name := "assetname"
content := "assetcontent"
downloadURL := "downloadURL"
now := now()
assetCreated := tick(&now)
size := len(content)
downloadCount := int64(10)
sha256 := fmt.Sprintf("%x", sha256.Sum256([]byte(content)))
return &f3.ReleaseAsset{
Name: name,
Size: int64(size),
DownloadCount: downloadCount,
Created: assetCreated,
SHA256: sha256,
DownloadURL: downloadURL,
DownloadFunc: func() io.ReadCloser {
rc := io.NopCloser(strings.NewReader(content))
return rc
},
}
}
func (f *Creator) GenerateReview(parent path.Path) *f3.Review {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs"))
repositoryNode.Get(context.Background())
repositoryHelper := tests_repository.NewTestHelper(f.t, "", repositoryNode)
now := now()
reviewCreated := tick(&now)
featureSha := repositoryHelper.GetRepositorySha("feature")
return &f3.Review{
ReviewerID: f3_tree.NewUserReference(user.GetID()),
Official: true,
CommitID: featureSha,
Content: "the review content",
CreatedAt: reviewCreated,
State: f3.ReviewStateCommented,
}
}
func (f *Creator) GenerateReviewComment(parent path.Path) *f3.ReviewComment {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs"))
repositoryNode.Get(context.Background())
repositoryHelper := tests_repository.NewTestHelper(f.t, "", repositoryNode)
now := now()
commentCreated := tick(&now)
commentUpdated := tick(&now)
featureSha := repositoryHelper.GetRepositorySha("feature")
return &f3.ReviewComment{
Content: "comment content",
TreePath: "README.md",
DiffHunk: "@@ -108,7 +108,6 @@",
Line: 1,
CommitID: featureSha,
PosterID: f3_tree.NewUserReference(user.GetID()),
CreatedAt: commentCreated,
UpdatedAt: commentUpdated,
}
}

260
tree/tests/f3/f3_test.go Normal file
View file

@ -0,0 +1,260 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package f3
import (
"context"
"crypto/sha256"
"fmt"
"io"
"path/filepath"
"sort"
"strings"
"testing"
"code.forgejo.org/f3/gof3/v3/f3"
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
tests_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/tests/repository"
"code.forgejo.org/f3/gof3/v3/options"
"code.forgejo.org/f3/gof3/v3/path"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
"github.com/stretchr/testify/assert"
)
func TestF3Mirror(t *testing.T) {
ctx := context.Background()
for _, factory := range tests_forge.GetFactories() {
testForge := factory()
t.Run(testForge.GetName(), func(t *testing.T) {
// testCase.options will t.Skip if the forge instance is not up
forgeWriteOptions := testForge.NewOptions(t)
forgeReadOptions := testForge.NewOptions(t)
forgeReadOptions.(options.URLInterface).SetURL(forgeWriteOptions.(options.URLInterface).GetURL())
fixtureTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
fixtureTree.Trace("======= build fixture")
TreeBuildPartial(t, "F3Mirror"+testForge.GetName(), testForge.GetKindExceptions(), forgeWriteOptions, fixtureTree)
fixtureTree.Trace("======= mirror fixture to forge")
forgeWriteTree := generic.GetFactory("f3")(ctx, forgeWriteOptions)
generic.TreeMirror(ctx, fixtureTree, forgeWriteTree, generic.NewPathFromString(""), generic.NewMirrorOptions())
paths := []string{""}
if testForge.GetName() != filesystem_options.Name {
paths = []string{"/forge/users/10111", "/forge/users/20222"}
}
pathPairs := make([][2]path.Path, 0, 5)
for _, p := range paths {
p := generic.NewPathFromString(p)
pathPairs = append(pathPairs, [2]path.Path{p, generic.TreePathRemap(ctx, fixtureTree, p, forgeWriteTree)})
}
fixtureTree.Trace("======= read from forge")
forgeReadTree := generic.GetFactory("f3")(ctx, forgeReadOptions)
forgeReadTree.WalkAndGet(ctx, generic.NewWalkOptions(nil))
fixtureTree.Trace("======= mirror forge to filesystem")
verificationTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
for _, pathPair := range pathPairs {
generic.TreeMirror(ctx, forgeReadTree, verificationTree, pathPair[1], generic.NewMirrorOptions())
}
fixtureTree.Trace("======= compare fixture with forge mirrored to filesystem")
for _, pathPair := range pathPairs {
assert.True(t, generic.TreeCompare(ctx, fixtureTree, pathPair[0], verificationTree, pathPair[1]))
}
TreeDelete(t, testForge.GetNonTestUsers(), forgeWriteOptions, forgeWriteTree)
})
}
}
func TestF3Tree(t *testing.T) {
ctx := context.Background()
verify := func(tree generic.TreeInterface, expected []string) {
collected := make([]string, 0, 10)
collect := func(ctx context.Context, path path.Path, node generic.NodeInterface) {
path = path.Append(node)
collected = append(collected, path.String())
}
tree.WalkAndGet(ctx, generic.NewWalkOptions(collect))
sort.Strings(expected)
sort.Strings(collected)
assert.EqualValues(t, expected, collected)
}
for _, testCase := range []struct {
name string
build func(tree generic.TreeInterface)
operations func(tree generic.TreeInterface)
expected []string
}{
{
name: "full tree",
build: func(tree generic.TreeInterface) { TreeBuild(t, "F3", nil, tree) },
operations: func(tree generic.TreeInterface) {},
expected: []string{
"",
"/forge",
"/forge/organizations",
"/forge/organizations/3330001",
"/forge/organizations/3330001/projects",
"/forge/topics",
"/forge/topics/14411441",
"/forge/users",
"/forge/users/10111",
"/forge/users/10111/projects",
"/forge/users/10111/projects/74823",
"/forge/users/10111/projects/74823/issues",
"/forge/users/10111/projects/74823/issues/1234567",
"/forge/users/10111/projects/74823/issues/1234567/comments",
"/forge/users/10111/projects/74823/issues/1234567/comments/1111999",
"/forge/users/10111/projects/74823/issues/1234567/comments/1111999/reactions",
"/forge/users/10111/projects/74823/issues/1234567/reactions",
"/forge/users/10111/projects/74823/issues/1234567/reactions/1212",
"/forge/users/10111/projects/74823/labels",
"/forge/users/10111/projects/74823/labels/7777",
"/forge/users/10111/projects/74823/labels/99999",
"/forge/users/10111/projects/74823/milestones",
"/forge/users/10111/projects/74823/milestones/7888",
"/forge/users/10111/projects/74823/pull_requests",
"/forge/users/10111/projects/74823/pull_requests/2222",
"/forge/users/10111/projects/74823/pull_requests/2222/comments",
"/forge/users/10111/projects/74823/pull_requests/2222/reactions",
"/forge/users/10111/projects/74823/pull_requests/2222/reviews",
"/forge/users/10111/projects/74823/pull_requests/2222/reviews/4593",
"/forge/users/10111/projects/74823/pull_requests/2222/reviews/4593/reactions",
"/forge/users/10111/projects/74823/pull_requests/2222/reviews/4593/reviewcomments",
"/forge/users/10111/projects/74823/pull_requests/2222/reviews/4593/reviewcomments/9876543",
"/forge/users/10111/projects/74823/releases",
"/forge/users/10111/projects/74823/releases/123",
"/forge/users/10111/projects/74823/releases/123/assets",
"/forge/users/10111/projects/74823/releases/123/assets/585858",
"/forge/users/10111/projects/74823/repositories",
"/forge/users/10111/projects/74823/repositories/vcs",
"/forge/users/20222",
"/forge/users/20222/projects",
"/forge/users/20222/projects/99099",
"/forge/users/20222/projects/99099/issues",
"/forge/users/20222/projects/99099/labels",
"/forge/users/20222/projects/99099/milestones",
"/forge/users/20222/projects/99099/pull_requests",
"/forge/users/20222/projects/99099/releases",
"/forge/users/20222/projects/99099/repositories",
"/forge/users/20222/projects/99099/repositories/vcs",
},
},
} {
t.Run(testCase.name, func(t *testing.T) {
tree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
tree.Trace("======= BUILD")
testCase.build(tree)
tree.Trace("======= OPERATIONS")
testCase.operations(tree)
tree.Trace("======= VERIFY")
verify(tree, testCase.expected)
tree.Clear(ctx)
tree.Trace("======= VERIFY STEP 2")
verify(tree, testCase.expected)
tree.Trace("======= COMPARE")
otherTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
otherTree.GetOptions().(options.URLInterface).SetURL(tree.GetOptions().(options.URLInterface).GetURL())
otherTree.WalkAndGet(ctx, generic.NewWalkOptions(nil))
assert.True(t, generic.TreeCompare(ctx, tree, generic.NewPathFromString(""), otherTree, generic.NewPathFromString("")))
})
}
}
func TestF3Repository(t *testing.T) {
ctx := context.Background()
verify := func(tree generic.TreeInterface, name string) {
repositoryPath := generic.NewPathFromString(filepath.Join("/forge/users/10111/projects/74823/repositories", name))
found := false
tree.Apply(ctx, repositoryPath, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
node.Get(ctx)
node.List(ctx)
if node.GetKind() != f3_tree.KindRepository {
return
}
helper := tests_repository.NewTestHelper(t, "", node)
helper.AssertRepositoryFileExists("README.md")
if name == f3.RepositoryNameDefault {
helper.AssertRepositoryTagExists("releasetagv12")
helper.AssertRepositoryBranchExists("feature")
}
found = true
}).SetWhere(generic.ApplyEachNode))
assert.True(t, found)
}
for _, name := range []string{f3.RepositoryNameDefault} {
t.Run(name, func(t *testing.T) {
tree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
tree.Trace("======= BUILD")
TreeBuild(t, "F3", nil, tree)
tree.Trace("======= VERIFY")
verify(tree, name)
tree.Clear(ctx)
tree.Trace("======= VERIFY STEP 2")
verify(tree, name)
})
}
}
func TestF3Asset(t *testing.T) {
ctx := context.Background()
content := "OTHER CONTENT"
expectedSHA256 := fmt.Sprintf("%x", sha256.Sum256([]byte(content)))
opts := tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t)
{
tree := generic.GetFactory("f3")(ctx, opts)
TreeBuild(t, "F3", nil, tree)
assetPath := generic.NewPathFromString("/forge/users/10111/projects/74823/releases/123/assets/585858")
asset := tree.Find(assetPath)
assert.False(t, asset.GetIsNil())
assetFormat := asset.ToFormat().(*f3.ReleaseAsset)
assetFormat.DownloadFunc = func() io.ReadCloser {
rc := io.NopCloser(strings.NewReader(content))
return rc
}
asset.FromFormat(assetFormat)
asset.Upsert(ctx)
assetFormat = asset.ToFormat().(*f3.ReleaseAsset)
assert.EqualValues(t, expectedSHA256, assetFormat.SHA256)
}
{
tree := generic.GetFactory("f3")(ctx, opts)
tree.WalkAndGet(ctx, generic.NewWalkOptions(nil))
assetPath := generic.NewPathFromString("/forge/users/10111/projects/74823/releases/123/assets/585858")
asset := tree.Find(assetPath)
assert.False(t, asset.GetIsNil())
assetFormat := asset.ToFormat().(*f3.ReleaseAsset)
assert.EqualValues(t, expectedSHA256, assetFormat.SHA256)
}
}

View file

@ -0,0 +1,105 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package f3
import (
"context"
"path/filepath"
"testing"
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/options"
"code.forgejo.org/f3/gof3/v3/path"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
"github.com/stretchr/testify/assert"
)
func TestF3FilesystemMappedID(t *testing.T) {
ctx := context.Background()
//
// aTree only has /forge/user/10111
//
aTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
aDir := aTree.GetOptions().(options.URLInterface).GetURL()
creator := NewCreator(t, "F3", aTree.GetLogger())
aF3Tree := aTree.(f3_tree.TreeInterface)
userID := "10111"
aF3Tree.CreateChild(ctx, "/", func(parent path.Path, forge generic.NodeInterface) {
forge.FromFormat(creator.GenerateForge())
})
aF3Tree.CreateChild(ctx, "/forge/users", func(parent path.Path, user generic.NodeInterface) {
user.FromFormat(GeneratorSetID(creator.GenerateUser(), userID))
})
//
// bTree mirrors aTree exactly
//
rootPath := generic.NewPathFromString("")
bTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
generic.TreeMirror(ctx, aTree, bTree, rootPath, generic.NewMirrorOptions())
assert.True(t, generic.TreeCompare(ctx, aTree, rootPath, bTree, rootPath))
//
// aTree maps user id 10111 to 10111.mapped
//
userPath := generic.NewPathFromString(filepath.Join("/forge/users", userID))
userMappedID := id.NewNodeID("10111.mapped")
assert.True(t, aTree.Apply(ctx, userPath, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
node.SetMappedID(userMappedID)
node.Upsert(ctx)
})))
aTree = generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
aTree.GetOptions().(options.URLInterface).SetURL(aDir)
aTree.WalkAndGet(ctx, generic.NewWalkOptions(nil))
assert.NotEqualValues(t, generic.NilNode, aTree.Find(userPath))
//
// cTree mirrors aTree with user id 10111 remapped to 10111.mapped
//
cTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
cDir := cTree.GetOptions().(options.URLInterface).GetURL()
generic.TreeMirror(ctx, aTree, cTree, rootPath, generic.NewMirrorOptions())
userMappedPath := generic.NewPathFromString(filepath.Join("/forge/users", userMappedID.String()))
assert.NotEqualValues(t, generic.NilNode, cTree.Find(userMappedPath))
assert.EqualValues(t, generic.NilNode, cTree.Find(userPath))
//
// reset cTree and read from the filesystem
//
cTree = generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
cTree.GetOptions().(options.URLInterface).SetURL(cDir)
cTree.WalkAndGet(ctx, generic.NewWalkOptions(nil))
assert.NotEqualValues(t, generic.NilNode, cTree.Find(userMappedPath))
assert.EqualValues(t, generic.NilNode, cTree.Find(userPath))
//
// delete aTree user
//
deleted := aTree.Find(userPath).Delete(ctx)
assert.EqualValues(t, userMappedID, deleted.GetMappedID())
assert.EqualValues(t, generic.NilNode, cTree.Find(userPath))
}

224
tree/tests/f3/fixture.go Normal file
View file

@ -0,0 +1,224 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package f3
import (
"context"
"path/filepath"
"slices"
"sort"
"testing"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/options"
"code.forgejo.org/f3/gof3/v3/path"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
var KindToFixturePath = map[kind.Kind]string{
f3_tree.KindTopics: "/forge/topics",
f3_tree.KindTopic: "/forge/topics/14411441",
f3_tree.KindUsers: "/forge/users",
f3_tree.KindUser: "/forge/users/10111",
f3_tree.KindProjects: "/forge/users/10111/projects",
f3_tree.KindProject: "/forge/users/10111/projects/74823",
f3_tree.KindLabels: "/forge/users/10111/projects/74823/labels",
f3_tree.KindLabel: "/forge/users/10111/projects/74823/labels/7777",
f3_tree.KindIssues: "/forge/users/10111/projects/74823/issues",
f3_tree.KindIssue: "/forge/users/10111/projects/74823/issues/1234567",
f3_tree.KindPullRequests: "/forge/users/10111/projects/74823/pull_requests",
f3_tree.KindPullRequest: "/forge/users/10111/projects/74823/pull_requests/2222",
f3_tree.KindReviews: "/forge/users/10111/projects/74823/pull_requests/2222/reviews",
f3_tree.KindReview: "/forge/users/10111/projects/74823/pull_requests/2222/reviews/4593",
f3_tree.KindReviewComments: "/forge/users/10111/projects/74823/pull_requests/2222/reviews/4593/reviewcomments",
f3_tree.KindReviewComment: "/forge/users/10111/projects/74823/pull_requests/2222/reviews/4593/reviewcomments/9876543",
f3_tree.KindMilestones: "/forge/users/10111/projects/74823/milestones",
f3_tree.KindMilestone: "/forge/users/10111/projects/74823/milestones/7888",
f3_tree.KindReactions: "/forge/users/10111/projects/74823/issues/1234567/reactions",
f3_tree.KindReaction: "/forge/users/10111/projects/74823/issues/1234567/reactions/1212",
f3_tree.KindComments: "/forge/users/10111/projects/74823/issues/1234567/comments",
f3_tree.KindComment: "/forge/users/10111/projects/74823/issues/1234567/comments/1111999",
f3_tree.KindRepositories: "/forge/users/10111/projects/74823/repositories",
f3_tree.KindRepository: "/forge/users/10111/projects/74823/repositories/vcs",
f3_tree.KindReleases: "/forge/users/10111/projects/74823/releases",
f3_tree.KindRelease: "/forge/users/10111/projects/74823/releases/123",
f3_tree.KindAssets: "/forge/users/10111/projects/74823/releases/123/assets",
f3_tree.KindAsset: "/forge/users/10111/projects/74823/releases/123/assets/585858",
f3_tree.KindOrganizations: "/forge/organizations",
f3_tree.KindOrganization: "/forge/organizations/3330001",
}
var KindWithFixturePath = SetKindWithFixturePath()
func SetKindWithFixturePath() []kind.Kind {
l := make([]kind.Kind, 0, len(KindToFixturePath))
for kind := range KindToFixturePath {
l = append(l, kind)
}
sort.Slice(l, func(i, j int) bool { return string(l[i]) < string(l[j]) })
return l
}
func TreeBuild(t *testing.T, name string, opts options.Interface, tree generic.TreeInterface) {
TreeBuildPartial(t, name, []kind.Kind{}, opts, tree)
}
func TreeBuildPartial(t *testing.T, name string, exceptions []kind.Kind, opts options.Interface, tree generic.TreeInterface) {
ctx := context.Background()
creator := NewCreator(t, name, tree.GetLogger())
f3Tree := tree.(f3_tree.TreeInterface)
url := "<unknown>"
if urlInterface, ok := opts.(options.URLInterface); ok {
url = urlInterface.GetURL()
}
f3Tree.CreateChild(ctx, "/", func(parent path.Path, forge generic.NodeInterface) {
f := creator.GenerateForge()
f.URL = url
forge.FromFormat(f)
})
if slices.Contains(exceptions, f3_tree.KindUsers) {
return
}
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindUsers], func(parent path.Path, user generic.NodeInterface) {
user.FromFormat(GeneratorSetID(creator.GenerateUser(), "10111"))
})
if slices.Contains(exceptions, f3_tree.KindProjects) {
return
}
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindProjects], func(parent path.Path, project generic.NodeInterface) {
project.FromFormat(GeneratorSetID(creator.GenerateProject(), "74823"))
})
if slices.Contains(exceptions, f3_tree.KindRepositories) {
return
}
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindRepositories], func(parent path.Path, repository generic.NodeInterface) {
repository.FromFormat(GeneratorSetID(creator.GenerateRepository(f3.RepositoryNameDefault), f3.RepositoryNameDefault))
})
if !slices.Contains(exceptions, f3_tree.KindReleases) {
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindReleases], func(parent path.Path, release generic.NodeInterface) {
release.FromFormat(GeneratorSetID(creator.GenerateRelease(parent), "123"))
})
if !slices.Contains(exceptions, f3_tree.KindAssets) {
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindAssets], func(parent path.Path, asset generic.NodeInterface) {
asset.FromFormat(GeneratorSetID(creator.GenerateAsset(parent), "585858"))
})
}
}
reviewerID := "20222"
{
userID := "20222"
f3Tree.CreateChild(ctx, "/forge/users", func(parent path.Path, user generic.NodeInterface) {
user.FromFormat(GeneratorSetID(creator.GenerateUser(), userID))
})
projectID := "99099"
f3Tree.CreateChild(ctx, filepath.Join("/forge/users", userID, "projects"), func(parent path.Path, user generic.NodeInterface) {
user.FromFormat(GeneratorSetID(creator.GenerateForkedProject(parent, KindToFixturePath[f3_tree.KindProject]), projectID))
})
f3Tree.CreateChild(ctx, filepath.Join("/forge/users", userID, "projects", projectID, "repositories"), func(parent path.Path, repository generic.NodeInterface) {
repository.FromFormat(GeneratorSetID(creator.GenerateRepository(f3.RepositoryNameDefault), f3.RepositoryNameDefault))
})
}
if !slices.Contains(exceptions, f3_tree.KindPullRequests) {
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindPullRequests], func(parent path.Path, pullRequest generic.NodeInterface) {
pullRequest.FromFormat(GeneratorSetID(creator.GeneratePullRequest(parent), "2222"))
})
if !slices.Contains(exceptions, f3_tree.KindReviews) {
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindReviews], func(parent path.Path, review generic.NodeInterface) {
reviewFormat := creator.GenerateReview(parent)
GeneratorSetID(reviewFormat, "4593")
reviewFormat.ReviewerID = f3_tree.NewUserReference(reviewerID)
review.FromFormat(reviewFormat)
})
if !slices.Contains(exceptions, f3_tree.KindReviewComments) {
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindReviewComments], func(parent path.Path, reviewComment generic.NodeInterface) {
reviewCommentFormat := creator.GenerateReviewComment(parent)
GeneratorSetID(reviewCommentFormat, "9876543")
reviewCommentFormat.PosterID = f3_tree.NewUserReference(reviewerID)
reviewComment.FromFormat(reviewCommentFormat)
})
}
}
}
if !slices.Contains(exceptions, f3_tree.KindLabels) {
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindLabels], func(parent path.Path, label generic.NodeInterface) {
label.FromFormat(GeneratorSetID(creator.GenerateLabel(), "99999"))
})
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindLabels], func(parent path.Path, label generic.NodeInterface) {
label.FromFormat(GeneratorSetID(creator.GenerateLabel(), "7777"))
})
}
if !slices.Contains(exceptions, f3_tree.KindMilestones) {
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindMilestones], func(parent path.Path, milestone generic.NodeInterface) {
milestone.FromFormat(GeneratorSetID(creator.GenerateMilestone(), "7888"))
})
}
if !slices.Contains(exceptions, f3_tree.KindIssues) {
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindIssues], func(parent path.Path, issue generic.NodeInterface) {
issue.FromFormat(GeneratorSetID(creator.GenerateIssue(parent), "1234567"))
})
if !slices.Contains(exceptions, f3_tree.KindComments) {
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindComments], func(parent path.Path, comment generic.NodeInterface) {
comment.FromFormat(GeneratorSetID(creator.GenerateComment(parent), "1111999"))
})
if !slices.Contains(exceptions, f3_tree.KindReactions) {
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindReactions], func(parent path.Path, reaction generic.NodeInterface) {
reaction.FromFormat(GeneratorSetID(creator.GenerateReaction(parent), "1212"))
})
}
}
}
if !slices.Contains(exceptions, f3_tree.KindOrganizations) {
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindOrganizations], func(parent path.Path, organization generic.NodeInterface) {
organization.FromFormat(GeneratorSetID(creator.GenerateOrganization(), "3330001"))
})
}
if !slices.Contains(exceptions, f3_tree.KindTopics) {
f3Tree.CreateChild(ctx, KindToFixturePath[f3_tree.KindTopics], func(parent path.Path, topic generic.NodeInterface) {
topic.FromFormat(GeneratorSetID(creator.GenerateTopic(), "14411441"))
})
}
}
func TreeDelete(t *testing.T, nonTestUsers []string, options options.Interface, tree generic.TreeInterface) {
ctx := context.Background()
for _, owners := range []path.Path{f3_tree.OrganizationsPath, f3_tree.UsersPath} {
for _, owner := range tree.Find(owners).List(ctx) {
if user, ok := owner.ToFormat().(*f3.User); ok {
if slices.Contains(nonTestUsers, user.UserName) {
continue
}
}
for _, project := range owner.Find(generic.NewPathFromString(f3_tree.KindProjects)).List(ctx) {
project.Delete(ctx)
}
owner.Delete(ctx)
}
}
}

View file

@ -0,0 +1,30 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forge
import (
"os"
"code.forgejo.org/f3/gof3/v3/kind"
)
const (
ComplianceNameForkedPullRequest = "forked_pull_request"
)
type Base struct {
name string
}
func (o *Base) GetName() string { return o.name }
func (o *Base) SetName(name string) { o.name = name }
func (o *Base) GetNonTestUsers() []string { return []string{} }
func (o *Base) GetKindExceptions() []kind.Kind { return nil }
func (o *Base) GetNameExceptions() []string { return nil }
func (o *Base) DeleteAfterCompliance() bool {
return os.Getenv("GOF3_TEST_COMPLIANCE_CLEANUP") != "false"
}

View file

@ -0,0 +1,35 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forge
import (
"fmt"
"strings"
)
type (
Factory func() Interface
Factories map[string]Factory
)
var factories = make(Factories, 10)
func GetFactories() Factories {
return factories
}
func RegisterFactory(name string, factory Factory) {
name = strings.ToLower(name)
factories[name] = factory
}
func GetFactory(name string) Factory {
name = strings.ToLower(name)
factory, ok := factories[name]
if !ok {
panic(fmt.Errorf("no factory registered for %s", name))
}
return factory
}

View file

@ -0,0 +1,26 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forge
import (
"testing"
"code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/options"
)
type Interface interface {
GetName() string
SetName(string)
DeleteAfterCompliance() bool
GetKindExceptions() []kind.Kind
GetNameExceptions() []string
GetNonTestUsers() []string
NewOptions(t *testing.T) options.Interface
}

View file

@ -0,0 +1,254 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package f3
import (
"context"
"slices"
"testing"
"time"
"code.forgejo.org/f3/gof3/v3/f3"
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
tests_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/tests/repository"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/path"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type remapper func(path string) path.Path
func ForgeCompliance(t *testing.T, name string) {
testForge := tests_forge.GetFactory(name)()
ctx := context.Background()
forgeOptions := testForge.NewOptions(t)
forgeTree := generic.GetFactory("f3")(ctx, forgeOptions)
forgeTree.Trace("======= build fixture")
fixtureTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
TreeBuildPartial(t, name, testForge.GetKindExceptions(), forgeOptions, fixtureTree)
forgeTree.Trace("======= mirror fixture to forge")
generic.TreeMirror(ctx, fixtureTree, forgeTree, generic.NewPathFromString(""), generic.NewMirrorOptions())
forgeTree.Trace("======= run compliance tests")
remap := func(p string) path.Path {
return generic.TreePathRemap(ctx, fixtureTree, generic.NewPathFromString(p), forgeTree)
}
kindToForgePath := make(map[kind.Kind]string, len(KindToFixturePath))
for kind, path := range KindToFixturePath {
remappedPath := remap(path)
if remappedPath.Empty() {
forgeTree.Trace("%s was not mirrored, ignored", path)
continue
}
kindToForgePath[kind] = remappedPath.String()
}
ComplianceKindTests(t, name, forgeTree.(f3_tree.TreeInterface), kindToForgePath, testForge.GetKindExceptions())
ComplianceNameTests(t, name, forgeTree.(f3_tree.TreeInterface), remap, kindToForgePath, testForge.GetNameExceptions())
if testForge.DeleteAfterCompliance() {
TreeDelete(t, testForge.GetNonTestUsers(), forgeOptions, forgeTree)
}
}
func ComplianceNameTests(t *testing.T, name string, tree f3_tree.TreeInterface, remap remapper, kindToForgePath map[kind.Kind]string, exceptions []string) {
t.Helper()
ctx := context.Background()
if !slices.Contains(exceptions, tests_forge.ComplianceNameForkedPullRequest) {
t.Run(tests_forge.ComplianceNameForkedPullRequest, func(t *testing.T) {
kind := kind.Kind(f3_tree.KindPullRequests)
p := kindToForgePath[kind]
parent := tree.Find(generic.NewPathFromString(p))
require.NotEqualValues(t, generic.NilNode, parent, p)
child := tree.Factory(ctx, tree.GetChildrenKind(kind))
childFormat := ComplianceForkedPullRequest(t, tree, remap, child.NewFormat().(*f3.PullRequest), parent.GetCurrentPath())
child.FromFormat(childFormat)
tree.Trace("'Upsert' the new forked pull request in the parent and store it in the forge")
child.SetParent(parent)
child.Upsert(ctx)
tree.Trace("'Delete' child forked pull request")
child.Delete(ctx)
})
}
}
func ComplianceForkedPullRequest(t *testing.T, tree f3_tree.TreeInterface, remap remapper, pullRequest *f3.PullRequest, parent path.Path) *f3.PullRequest {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs"))
repositoryNode.Get(context.Background())
repositoryHelper := tests_repository.NewTestHelper(t, "", repositoryNode)
mainRef := "master"
mainSha := repositoryHelper.GetRepositorySha(mainRef)
tree.Trace("create a feature branch in the /forge/users/20222/projects/99099 fork")
forkedRepositoryPath := remap("/forge/users/20222/projects/99099/repositories/vcs")
forkedRepositoryNode := tree.Find(forkedRepositoryPath)
require.NotEqual(t, forkedRepositoryNode, generic.NilNode)
forkedRepositoryHelper := tests_repository.NewTestHelper(t, "", forkedRepositoryNode)
featureRef := "generatedforkfeature"
forkedRepositoryHelper.InternalBranchRepositoryFeature(featureRef, featureRef+" content")
featureSha := forkedRepositoryHelper.GetRepositorySha(featureRef)
forkedRepositoryHelper.PushMirror()
now := now()
prCreated := tick(&now)
prUpdated := tick(&now)
pullRequest.PosterID = f3_tree.NewUserReference(user.GetID())
pullRequest.Title = featureRef + " pr title"
pullRequest.Content = featureRef + " pr content"
pullRequest.State = f3.PullRequestStateOpen
pullRequest.IsLocked = false
pullRequest.Created = prCreated
pullRequest.Updated = prUpdated
pullRequest.Closed = nil
pullRequest.Merged = false
pullRequest.MergedTime = nil
pullRequest.MergeCommitSHA = ""
pullRequest.Head = f3.PullRequestBranch{
Ref: featureRef,
SHA: featureSha,
Repository: f3.NewReference(forkedRepositoryPath.String()),
}
pullRequest.Base = f3.PullRequestBranch{
Ref: mainRef,
SHA: mainSha,
Repository: f3.NewReference("../../repository/vcs"),
}
return pullRequest
}
func ComplianceKindTests(t *testing.T, name string, tree f3_tree.TreeInterface, kindToForgePath map[kind.Kind]string, exceptions []kind.Kind) {
t.Helper()
exceptions = append(exceptions, f3_tree.KindRepositories)
for _, kind := range KindWithFixturePath {
path := kindToForgePath[kind]
if !tree.IsContainer(kind) {
continue
}
if slices.Contains(exceptions, kind) {
continue
}
t.Run(string(kind), func(t *testing.T) {
Compliance(t, name, tree, path, kind, GeneratorSetRandom, GeneratorModify)
})
}
}
func Compliance(t *testing.T, name string, tree f3_tree.TreeInterface, p string, kind kind.Kind, generator GeneratorFunc, modificator ModificatorFunc) {
t.Helper()
ctx := context.Background()
tree.Trace("%s", p)
parent := tree.Find(generic.NewPathFromString(p))
require.NotEqualValues(t, generic.NilNode, parent, p)
tree.Trace("create a new child in memory")
child := tree.Factory(ctx, tree.GetChildrenKind(kind))
childFormat := generator(t, name, child.NewFormat(), parent.GetCurrentPath())
child.FromFormat(childFormat)
if i := child.GetID(); i != id.NilID {
tree.Trace("about to insert child %s", i)
assert.EqualValues(t, generic.NilNode, parent.GetChild(child.GetID()))
} else {
tree.Trace("about to insert child with nil ID")
}
if child.GetDriver().IsNull() {
t.Skip("no driver, skipping")
}
tree.Trace("'Upsert' the new child in the parent and store it in the forge")
child.SetParent(parent)
child.Upsert(ctx)
tree.Trace("done inserting child '%s'", child.GetID())
before := child.ToFormat()
require.EqualValues(t, before.GetID(), child.GetID().String())
tree.Trace("'Get' the child '%s' from the forge", child.GetID())
child.Get(ctx)
after := child.ToFormat()
tree.Trace("check the F3 representations Upsert & Get to/from the forge are equivalent")
require.True(t, cmp.Equal(before, after), cmp.Diff(before, after))
tree.Trace("check the F3 representation FromFormat/ToFormat are identical")
{
saved := after
a := childFormat.Clone()
if tree.AllocateID() {
a.SetID("123456")
}
child.FromFormat(a)
b := child.ToFormat()
require.True(t, cmp.Equal(a, b), cmp.Diff(a, b))
child.FromFormat(saved)
}
if childFormat.GetName() != childFormat.GetID() {
tree.Trace("'GetIDFromName' %s %s", kind, childFormat.GetName())
id := parent.GetIDFromName(ctx, childFormat.GetName())
assert.EqualValues(t, child.GetID(), id)
}
for i, modified := range modificator(t, after, parent.GetCurrentPath()) {
tree.Trace("%d: %s 'Upsert' a modified child %v", i, kind, modified)
child.FromFormat(modified)
child.Upsert(ctx)
tree.Trace("%d: 'Get' the modified child '%s' from the forge", i, child.GetID())
child.Get(ctx)
after = child.ToFormat()
tree.Trace("%d: check the F3 representations Upsert & Get to/from the forge of the modified child are equivalent", i)
require.True(t, cmp.Equal(modified, after), cmp.Diff(modified, after))
}
nodeChildren := parent.GetNodeChildren()
tree.Trace("'ListPage' and only 'Get' known %d children of %s", len(nodeChildren), parent.GetKind())
if len(nodeChildren) > 0 {
parent.List(ctx)
for _, child := range parent.GetChildren() {
if _, ok := nodeChildren[child.GetID()]; ok {
tree.Trace("'WalkAndGet' %s child %s %s", parent.GetCurrentPath().ReadableString(), child.GetKind(), child.GetID())
child.WalkAndGet(ctx, parent.GetCurrentPath(), generic.NewWalkOptions(nil))
}
}
}
tree.Trace("'Delete' child '%s' from the forge", child.GetID())
child.Delete(ctx)
assert.EqualValues(t, generic.NilNode, parent.GetChild(child.GetID()))
assert.True(t, child.GetIsSync())
loop := 100
for i := 0; i < loop; i++ {
child.SetIsSync(false)
child.Get(ctx)
if !child.GetIsSync() {
break
}
tree.Trace("waiting for asynchronous child deletion (%d/%d)", i, loop)
time.Sleep(5 * time.Second)
}
assert.False(t, child.GetIsSync())
tree.Trace("%s did something %s", kind, child)
}

View file

@ -0,0 +1,19 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package f3
import (
"testing"
tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
)
func TestF3Forge(t *testing.T) {
for name := range tests_forge.GetFactories() {
t.Run(name, func(t *testing.T) {
ForgeCompliance(t, name)
})
}
}

474
tree/tests/f3/generator.go Normal file
View file

@ -0,0 +1,474 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package f3
import (
"context"
"crypto/sha256"
"fmt"
"io"
"strings"
"testing"
"time"
"code.forgejo.org/f3/gof3/v3/f3"
tests_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/tests/repository"
"code.forgejo.org/f3/gof3/v3/path"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
func GeneratorSetRandomID(id string, f f3.Interface, parent path.Path) f3.Interface {
if parent.First().(generic.NodeInterface).GetTree().AllocateID() {
return f
}
return GeneratorSetID(f, id)
}
func GeneratorSetID(f f3.Interface, id string) f3.Interface {
f.SetID(id)
return f
}
type ModificatorFunc func(t *testing.T, f f3.Interface, parent path.Path) []f3.Interface
func GeneratorModify(t *testing.T, f f3.Interface, parent path.Path) []f3.Interface {
switch v := f.(type) {
case *f3.User:
return GeneratorModifyUser(v)
case *f3.Organization:
return GeneratorModifyOrganization(v)
case *f3.Project:
return GeneratorModifyProject(v, parent)
case *f3.Issue:
return GeneratorModifyIssue(v, parent)
case *f3.Milestone:
return GeneratorModifyMilestone(v, parent)
case *f3.Topic:
return GeneratorModifyTopic(v, parent)
case *f3.Reaction:
// a reaction cannot be modified, it can only be created and deleted
return []f3.Interface{}
case *f3.Label:
return GeneratorModifyLabel(v, parent)
case *f3.Comment:
return GeneratorModifyComment(v, parent)
case *f3.Release:
return GeneratorModifyRelease(t, v, parent)
case *f3.ReleaseAsset:
return GeneratorModifyReleaseAsset(v, parent)
case *f3.PullRequest:
return GeneratorModifyPullRequest(t, v, parent)
case *f3.Review:
// a review cannot be modified, it can only be created and deleted
return []f3.Interface{}
case *f3.ReviewComment:
return GeneratorModifyReviewComment(t, v, parent)
default:
panic(fmt.Errorf("not implemented %T", f))
}
}
type GeneratorFunc func(t *testing.T, name string, f f3.Interface, parent path.Path) f3.Interface
func GeneratorSetRandom(t *testing.T, name string, f f3.Interface, parent path.Path) f3.Interface {
GeneratorSetRandomID(name, f, parent)
switch v := f.(type) {
case *f3.User:
return GeneratorSetRandomUser(v)
case *f3.Organization:
return GeneratorSetRandomOrganization(v)
case *f3.Project:
return GeneratorSetRandomProject(v, parent)
case *f3.Issue:
return GeneratorSetRandomIssue(v, parent)
case *f3.Milestone:
return GeneratorSetRandomMilestone(v, parent)
case *f3.Topic:
return GeneratorSetRandomTopic(v, parent)
case *f3.Reaction:
return GeneratorSetRandomReaction(v, parent)
case *f3.Label:
return GeneratorSetRandomLabel(v, parent)
case *f3.Comment:
return GeneratorSetRandomComment(v, parent)
case *f3.Release:
return GeneratorSetRandomRelease(t, v, parent)
case *f3.ReleaseAsset:
return GeneratorSetRandomReleaseAsset(v, parent)
case *f3.PullRequest:
return GeneratorSetRandomPullRequest(t, v, parent)
case *f3.Review:
return GeneratorSetReview(t, v, parent)
case *f3.ReviewComment:
return GeneratorSetReviewComment(t, v, parent)
default:
panic(fmt.Errorf("not implemented %T", f))
}
}
func GeneratorSetRandomUser(user *f3.User) *f3.User {
username := fmt.Sprintf("generateduser%s", user.GetID())
user.Name = username + " Doe"
user.UserName = username
user.Email = username + "@example.com"
user.Password = "Wrobyak4"
return user
}
func GeneratorModifyUser(user *f3.User) []f3.Interface {
return []f3.Interface{user.Clone()}
}
func GeneratorSetRandomOrganization(organization *f3.Organization) *f3.Organization {
organizationname := fmt.Sprintf("generatedorg%s", organization.GetID())
organization.FullName = organizationname + " Lambda"
organization.Name = organizationname
return organization
}
func GeneratorModifyOrganization(organization *f3.Organization) []f3.Interface {
organization0 := organization.Clone().(*f3.Organization)
organization0.FullName = "modified " + organization.FullName
return []f3.Interface{organization0}
}
func GeneratorSetRandomProject(project *f3.Project, parent path.Path) *f3.Project {
projectname := fmt.Sprintf("project%s", project.GetID())
project.Name = projectname
project.IsPrivate = false
project.IsMirror = false
project.Description = "project description"
project.DefaultBranch = "main"
return project
}
func GeneratorModifyProject(project *f3.Project, parent path.Path) []f3.Interface {
project0 := project.Clone().(*f3.Project)
project0.Description = "modified " + project.Description
return []f3.Interface{project0}
}
func GeneratorSetRandomIssue(issue *f3.Issue, parent path.Path) *f3.Issue {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
labelsNode := projectNode.Find(generic.NewPathFromString("labels"))
labels := labelsNode.GetChildren()
firstLabel := labels[0]
now := now()
updated := tick(&now)
closed := tick(&now)
created := tick(&now)
userRef := f3_tree.NewUserReference(user.GetID())
labelRef := f3_tree.NewIssueLabelReference(firstLabel.GetID())
milestonesNode := projectNode.Find(generic.NewPathFromString("milestones"))
milestones := milestonesNode.GetChildren()
firstMilestone := milestones[0]
issue.PosterID = userRef
issue.Assignees = []*f3.Reference{userRef}
issue.Labels = []*f3.Reference{labelRef}
issue.Milestone = f3_tree.NewIssueMilestoneReference(firstMilestone.GetID())
issue.Title = "title"
issue.Content = "content"
issue.State = f3.IssueStateOpen
issue.IsLocked = false
issue.Created = created
issue.Updated = updated
issue.Closed = &closed
return issue
}
func GeneratorModifyIssue(issue *f3.Issue, parent path.Path) []f3.Interface {
assignees := issue.Assignees
milestone := issue.Milestone
labels := issue.Labels
issue0 := issue.Clone().(*f3.Issue)
issue0.Title = "modified " + issue.Title
issue0.Content = "modified " + issue.Content
issueClosed := issue0.Clone().(*f3.Issue)
issueClosed.Assignees = []*f3.Reference{}
issueClosed.Milestone = &f3.Reference{}
issueClosed.Labels = []*f3.Reference{}
issueClosed.State = f3.IssueStateClosed
issueClosed.IsLocked = true
issueOpen := issue0.Clone().(*f3.Issue)
issueOpen.Assignees = assignees
issueOpen.Milestone = milestone
issueOpen.Labels = labels
issueOpen.State = f3.IssueStateOpen
issueClosed.IsLocked = false
return []f3.Interface{
issue0,
issueClosed,
issueOpen,
}
}
func GeneratorSetRandomMilestone(milestone *f3.Milestone, parent path.Path) *f3.Milestone {
now := now()
created := tick(&now)
updated := tick(&now)
deadline := tick(&now)
title := fmt.Sprintf("milestone%s", milestone.GetID())
milestone.Title = title
milestone.Description = title + " description"
milestone.Deadline = &deadline
milestone.Created = created
milestone.Updated = &updated
milestone.Closed = nil
milestone.State = f3.MilestoneStateOpen
return milestone
}
func GeneratorModifyMilestone(milestone *f3.Milestone, parent path.Path) []f3.Interface {
milestone0 := milestone.Clone().(*f3.Milestone)
milestone0.Title = "modified " + milestone.Title
milestoneClosed := milestone0.Clone().(*f3.Milestone)
milestoneClosed.State = f3.MilestoneStateClosed
deadline := time.Now().Truncate(time.Second).Add(5 * time.Minute)
milestoneClosed.Deadline = &deadline
milestoneOpen := milestone0.Clone().(*f3.Milestone)
milestoneOpen.State = f3.MilestoneStateOpen
return []f3.Interface{
milestone0,
milestoneClosed,
milestoneOpen,
}
}
func GeneratorSetRandomTopic(topic *f3.Topic, parent path.Path) *f3.Topic {
topic.Name = fmt.Sprintf("topic%s", topic.GetID())
return topic
}
func GeneratorModifyTopic(topic *f3.Topic, parent path.Path) []f3.Interface {
topic0 := topic.Clone().(*f3.Topic)
return []f3.Interface{topic0}
}
func GeneratorSetRandomReaction(reaction *f3.Reaction, parent path.Path) *f3.Reaction {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
reaction.UserID = f3_tree.NewUserReference(user.GetID())
reaction.Content = "laugh"
return reaction
}
func GeneratorSetRandomLabel(label *f3.Label, parent path.Path) *f3.Label {
name := fmt.Sprintf("label%s", label.GetID())
label.Name = name
label.Description = name + " description"
label.Color = "ffffff"
return label
}
func GeneratorModifyLabel(label *f3.Label, parent path.Path) []f3.Interface {
label0 := label.Clone().(*f3.Label)
label0.Name = "modified" + label.Name
label0.Color = "f0f0f0"
label0.Description = "modified " + label.Description
return []f3.Interface{label0}
}
func GeneratorSetRandomComment(comment *f3.Comment, parent path.Path) *f3.Comment {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
now := now()
commentCreated := tick(&now)
commentUpdated := tick(&now)
comment.PosterID = f3_tree.NewUserReference(user.GetID())
comment.Created = commentCreated
comment.Updated = commentUpdated
comment.Content = "comment content"
return comment
}
func GeneratorModifyComment(comment *f3.Comment, parent path.Path) []f3.Interface {
comment0 := comment.Clone().(*f3.Comment)
comment0.Content = "modified" + comment.Content
return []f3.Interface{comment0}
}
func GeneratorSetRandomRelease(t *testing.T, release *f3.Release, parent path.Path) *f3.Release {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
project := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
repository := project.Find(generic.NewPathFromString("repositories/vcs"))
repository.Get(context.Background())
repositoryHelper := tests_repository.NewTestHelper(t, "", repository)
now := now()
releaseCreated := tick(&now)
tag := fmt.Sprintf("release%s", release.GetID())
repositoryHelper.CreateRepositoryTag(tag, "master")
sha := repositoryHelper.GetRepositorySha("master")
fmt.Printf("GeneratorSetRandomRelease %s %s\n", repository.GetCurrentPath(), repository.GetID())
repositoryHelper.PushMirror()
release.TagName = tag
release.TargetCommitish = sha
release.Name = tag + " name"
release.Body = tag + " body"
release.Draft = false
release.Prerelease = false
release.PublisherID = f3_tree.NewUserReference(user.GetID())
release.Created = releaseCreated
return release
}
func GeneratorModifyRelease(t *testing.T, release *f3.Release, parent path.Path) []f3.Interface {
release0 := release.Clone().(*f3.Release)
release0.Body = "modified " + release.Body
return []f3.Interface{release0}
}
func GeneratorSetRandomReleaseAsset(asset *f3.ReleaseAsset, parent path.Path) *f3.ReleaseAsset {
name := fmt.Sprintf("assetname%s", asset.GetID())
content := fmt.Sprintf("assetcontent%s", asset.GetID())
downloadURL := "downloadURL"
now := now()
assetCreated := tick(&now)
size := len(content)
downloadCount := int64(10)
sha256 := fmt.Sprintf("%x", sha256.Sum256([]byte(content)))
asset.Name = name
asset.Size = int64(size)
asset.DownloadCount = downloadCount
asset.Created = assetCreated
asset.SHA256 = sha256
asset.DownloadURL = downloadURL
asset.DownloadFunc = func() io.ReadCloser {
rc := io.NopCloser(strings.NewReader(content))
return rc
}
return asset
}
func GeneratorModifyReleaseAsset(asset *f3.ReleaseAsset, parent path.Path) []f3.Interface {
asset0 := asset.Clone().(*f3.ReleaseAsset)
asset0.Name = "modified" + asset.Name
return []f3.Interface{asset0}
}
func GeneratorSetRandomPullRequest(t *testing.T, pullRequest *f3.PullRequest, parent path.Path) *f3.PullRequest {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs"))
repositoryNode.Get(context.Background())
repositoryHelper := tests_repository.NewTestHelper(t, "", repositoryNode)
mainRef := "master"
mainSha := repositoryHelper.GetRepositorySha(mainRef)
featureRef := "generatedfeature"
repositoryHelper.InternalBranchRepositoryFeature(featureRef, featureRef+" content")
featureSha := repositoryHelper.GetRepositorySha(featureRef)
fmt.Printf("createPullRequest: master %s at main %s feature %s\n", repositoryHelper.GetBare(), mainSha, featureSha)
repositoryHelper.PushMirror()
now := now()
prCreated := tick(&now)
prUpdated := tick(&now)
pullRequest.PosterID = f3_tree.NewUserReference(user.GetID())
pullRequest.Title = featureRef + " pr title"
pullRequest.Content = featureRef + " pr content"
pullRequest.State = f3.PullRequestStateOpen
pullRequest.IsLocked = false
pullRequest.Created = prCreated
pullRequest.Updated = prUpdated
pullRequest.Closed = nil
pullRequest.Merged = false
pullRequest.MergedTime = nil
pullRequest.MergeCommitSHA = ""
pullRequest.Head = f3.PullRequestBranch{
Ref: featureRef,
SHA: featureSha,
Repository: f3.NewReference("../../repository/vcs"),
}
pullRequest.Base = f3.PullRequestBranch{
Ref: mainRef,
SHA: mainSha,
Repository: f3.NewReference("../../repository/vcs"),
}
return pullRequest
}
func GeneratorModifyPullRequest(t *testing.T, pullRequest *f3.PullRequest, parent path.Path) []f3.Interface {
pullRequest0 := pullRequest.Clone().(*f3.PullRequest)
pullRequest0.Title = "modified " + pullRequest.Title
return []f3.Interface{pullRequest0}
}
func GeneratorSetReview(t *testing.T, review *f3.Review, parent path.Path) *f3.Review {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs"))
repositoryNode.Get(context.Background())
repositoryHelper := tests_repository.NewTestHelper(t, "", repositoryNode)
now := now()
reviewCreated := tick(&now)
featureSha := repositoryHelper.GetRepositorySha("feature")
review.ReviewerID = f3_tree.NewUserReference(user.GetID())
review.Official = true
review.CommitID = featureSha
review.Content = "the review content"
review.CreatedAt = reviewCreated
review.State = f3.ReviewStateCommented
return review
}
func GeneratorSetReviewComment(t *testing.T, comment *f3.ReviewComment, parent path.Path) *f3.ReviewComment {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last().(generic.NodeInterface))
projectNode := f3_tree.GetFirstNodeKind(parent.Last().(generic.NodeInterface), f3_tree.KindProject)
repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs"))
repositoryNode.Get(context.Background())
repositoryHelper := tests_repository.NewTestHelper(t, "", repositoryNode)
now := now()
commentCreated := tick(&now)
commentUpdated := tick(&now)
featureSha := repositoryHelper.GetRepositorySha("feature")
comment.Content = "comment content"
comment.TreePath = "README.md"
comment.DiffHunk = "@@ -108,7 +108,6 @@"
comment.Line = 1
comment.CommitID = featureSha
comment.PosterID = f3_tree.NewUserReference(user.GetID())
comment.CreatedAt = commentCreated
comment.UpdatedAt = commentUpdated
return comment
}
func GeneratorModifyReviewComment(t *testing.T, comment *f3.ReviewComment, parent path.Path) []f3.Interface {
comment0 := comment.Clone().(*f3.ReviewComment)
comment0.Content = "modified " + comment.Content
return []f3.Interface{comment0}
}

View file

@ -0,0 +1,125 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package f3
import (
"context"
"testing"
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
forgejo_options "code.forgejo.org/f3/gof3/v3/forges/forgejo/options"
helpers_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/repository"
tests_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/tests/repository"
"code.forgejo.org/f3/gof3/v3/logger"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNodeRepositoryDriverMirrorNoop(t *testing.T) {
ctx := context.Background()
url := t.TempDir()
repositoryHelper := tests_repository.NewTestHelper(t, url, nil)
repositoryHelper.CreateRepositoryContent("").PushMirror()
log := logger.NewCaptureLogger()
log.SetLevel(logger.Trace)
log.Reset()
helpers_repository.GitMirror(ctx, log, url, url, []string{})
assert.Contains(t, log.String(), "do nothing")
log.Reset()
helpers_repository.GitMirrorRef(ctx, log, url, "fakeref", url, "fakeref")
assert.Contains(t, log.String(), "do nothing")
}
func TestNodeRepositoryDriverMirrorForgejo(t *testing.T) {
ctx := context.Background()
log := logger.NewCaptureLogger()
log.SetLevel(logger.Trace)
testForgejo := tests_forge.GetFactory(forgejo_options.Name)()
opts := testForgejo.NewOptions(t)
forgeTree := generic.GetFactory("f3")(ctx, opts)
forgeTree.Trace("======= build fixture")
fixtureTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
TreeBuildPartial(t, "F3", testForgejo.GetKindExceptions(), opts, fixtureTree)
forgeTree.Trace("======= mirror fixture to forge")
generic.TreeMirror(ctx, fixtureTree, forgeTree, generic.NewPathFromString(""), generic.NewMirrorOptions())
forgeTree.Trace("======= NodeRepositoryDriverMirror from a forgejo project to a directory")
repositoryPath := generic.TreePathRemap(ctx, fixtureTree, generic.NewPathFromString("/forge/users/10111/projects/74823/repositories/vcs"), forgeTree)
require.False(t, repositoryPath.Empty())
repository := forgeTree.Find(repositoryPath)
repositoryURL := repository.GetDriver().(f3_tree.RepositoryNodeDriverProxyInterface).GetRepositoryURL()
internalRefs := repository.GetDriver().(f3_tree.RepositoryNodeDriverProxyInterface).GetRepositoryInternalRefs()
log.Reset()
destination := t.TempDir()
tests_repository.NewTestHelper(t, destination, nil)
helpers_repository.GitMirror(ctx, log, repositoryURL, destination, internalRefs)
require.Contains(t, log.String(), "fetch fetchMirror")
forgeTree.Trace("======= NodeRepositoryDriverMirror from one forgejo project to another forgejo project")
otherRepositoryPath := generic.TreePathRemap(ctx, fixtureTree, generic.NewPathFromString("/forge/users/20222/projects/99099/repositories/vcs"), forgeTree)
require.False(t, otherRepositoryPath.Empty())
otherRepository := forgeTree.Find(otherRepositoryPath)
otherRepositoryURL := otherRepository.GetDriver().(f3_tree.RepositoryNodeDriverProxyInterface).GetRepositoryURL()
otherRepositoryPushURL := otherRepository.GetDriver().(f3_tree.RepositoryNodeDriverProxyInterface).GetRepositoryPushURL()
otherInternalRefs := otherRepository.GetDriver().(f3_tree.RepositoryNodeDriverProxyInterface).GetRepositoryInternalRefs()
log.Reset()
helpers_repository.GitMirror(ctx, log, repositoryURL, otherRepositoryPushURL, otherInternalRefs)
require.Contains(t, log.String(), "+refs/")
forgeTree.Trace("======= NodeRepositoryDriverMirror from a directory to a forgejo project")
log.Reset()
repositoryHelper := tests_repository.NewTestHelper(t, destination, nil)
content := "SOMETHING DIFFERENT"
repositoryHelper.CreateRepositoryContent(content).PushMirror()
helpers_repository.GitMirror(ctx, log, destination, otherRepositoryPushURL, otherInternalRefs)
require.Contains(t, log.String(), "+refs/")
verificationDir := t.TempDir()
repositoryHelper = tests_repository.NewTestHelper(t, verificationDir, nil)
helpers_repository.GitMirror(ctx, log, otherRepositoryURL, verificationDir, []string{})
repositoryHelper.AssertReadmeContains(content)
forgeTree.Trace("======= NodeRepositoryDriverMirrorRef from a forgejo project to a directory")
masterRef := "refs/heads/master"
forgejoRef := "refs/forgejo/test"
directoryRef := "refs/directory/test"
log.Reset()
helpers_repository.GitMirrorRef(ctx, log, repositoryURL, masterRef, destination, directoryRef)
require.Contains(t, log.String(), "new branch")
directorySha := helpers_repository.GitGetSha(ctx, log, destination, directoryRef)
forgeTree.Trace("======= NodeRepositoryDriverMirrorRef from one forgejo project to another forgejo project")
log.Reset()
otherForgejoRef := "refs/otherforgejo/test"
otherDirectoryRef := "refs/otherdirectory/test"
helpers_repository.GitMirrorRef(ctx, log, repositoryURL, masterRef, otherRepositoryPushURL, otherForgejoRef)
helpers_repository.GitMirrorRef(ctx, log, otherRepositoryURL, otherForgejoRef, destination, otherDirectoryRef)
assert.EqualValues(t, directorySha, helpers_repository.GitGetSha(ctx, log, destination, otherDirectoryRef))
forgeTree.Trace("======= NodeRepositoryDriverMirrorRef from a directory to a forgejo project")
log.Reset()
helpers_repository.GitMirrorRef(ctx, log, verificationDir, masterRef, otherRepositoryPushURL, forgejoRef)
masterSha := helpers_repository.GitGetSha(ctx, log, verificationDir, masterRef)
helpers_repository.GitMirrorRef(ctx, log, otherRepositoryURL, forgejoRef, verificationDir, directoryRef)
assert.EqualValues(t, masterSha, helpers_repository.GitGetSha(ctx, log, verificationDir, directoryRef))
}

19
tree/tests/f3/init.go Normal file
View file

@ -0,0 +1,19 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package f3
import (
// register filesystem test factory
_ "code.forgejo.org/f3/gof3/v3/forges/filesystem"
_ "code.forgejo.org/f3/gof3/v3/forges/filesystem/tests"
// register forgejo test factory
_ "code.forgejo.org/f3/gof3/v3/forges/forgejo"
_ "code.forgejo.org/f3/gof3/v3/forges/forgejo/tests"
// register gitlab test factory
_ "code.forgejo.org/f3/gof3/v3/forges/gitlab"
_ "code.forgejo.org/f3/gof3/v3/forges/gitlab/tests"
)

View file

@ -0,0 +1,16 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package f3
import (
"github.com/stretchr/testify/assert"
)
type TestingT interface {
assert.TestingT
TempDir() string
Skip(args ...any)
FailNow()
}

View file

@ -0,0 +1,9 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package f3
import (
_ "code.forgejo.org/f3/gof3/v3/forges"
)

View file

@ -0,0 +1,71 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package f3
import (
"context"
"testing"
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
"code.forgejo.org/f3/gof3/v3/options"
"code.forgejo.org/f3/gof3/v3/path"
"code.forgejo.org/f3/gof3/v3/tree/generic"
tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
"github.com/stretchr/testify/assert"
)
func TestF3PullRequest(t *testing.T) {
ctx := context.Background()
for _, factory := range tests_forge.GetFactories() {
testForge := factory()
t.Run(testForge.GetName(), func(t *testing.T) {
// testCase.options will t.Skip if the forge instance is not up
forgeWriteOptions := testForge.NewOptions(t)
forgeReadOptions := testForge.NewOptions(t)
forgeReadOptions.(options.URLInterface).SetURL(forgeWriteOptions.(options.URLInterface).GetURL())
fixtureTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
fixtureTree.Trace("======= build fixture")
TreeBuildPartial(t, "F3PullRequest"+testForge.GetName(), testForge.GetKindExceptions(), forgeWriteOptions, fixtureTree)
// craft a PR condition depending on testCase
fixtureTree.Trace("======= mirror fixture to forge")
forgeWriteTree := generic.GetFactory("f3")(ctx, forgeWriteOptions)
generic.TreeMirror(ctx, fixtureTree, forgeWriteTree, generic.NewPathFromString(""), generic.NewMirrorOptions())
paths := []string{"/forge/users/10111/projects/74823/repositories", "/forge/users/10111/projects/74823/pull_requests"}
pathPairs := make([][2]path.Path, 0, 5)
for _, p := range paths {
p := generic.NewPathFromString(p)
pathPairs = append(pathPairs, [2]path.Path{p, generic.TreePathRemap(ctx, fixtureTree, p, forgeWriteTree)})
}
fixtureTree.Trace("======= read from forge")
forgeReadTree := generic.GetFactory("f3")(ctx, forgeReadOptions)
forgeReadTree.WalkAndGet(ctx, generic.NewWalkOptions(nil))
fixtureTree.Trace("======= mirror forge to filesystem")
verificationTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
for _, pathPair := range pathPairs {
generic.TreeMirror(ctx, forgeReadTree, verificationTree, pathPair[1], generic.NewMirrorOptions())
}
fixtureTree.Trace("======= compare fixture with forge mirrored to filesystem")
for _, pathPair := range pathPairs {
fixtureTree.Trace("======= compare %s with %s", pathPair[0], pathPair[1])
assert.True(t, generic.TreeCompare(ctx, fixtureTree, pathPair[0], verificationTree, pathPair[1]))
}
TreeDelete(t, testForge.GetNonTestUsers(), forgeWriteOptions, forgeWriteTree)
})
}
}

View file

@ -0,0 +1,74 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"testing"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/tree/memory"
"github.com/stretchr/testify/assert"
)
func TestCompare(t *testing.T) {
ctx := context.Background()
aTree := NewMemoryTree(ctx, "O")
testTreeBuild(t, aTree, 2)
bTree := NewMemoryTree(ctx, "O")
testTreeBuild(t, bTree, 2)
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
{
toDelete := generic.NewPathFromString("/O-A/O-B")
aTree.Find(toDelete).Delete(ctx)
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
bTree.Find(toDelete).Delete(ctx)
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
}
{
content := "OTHER CONTENT"
toModify := generic.NewPathFromString("/O-A/O-F")
aNode := aTree.Find(toModify)
memory.SetContent(aNode, content)
aNode.Upsert(ctx)
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
bNode := bTree.Find(toModify)
memory.SetContent(bNode, content)
bNode.Upsert(ctx)
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
}
{
toModify := generic.NewPathFromString("/O-A/O-F")
aTree.Find(toModify).SetKind(kind.Kind("???"))
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
bTree.Find(toModify).SetKind(kind.Kind("???"))
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
}
{
pathToMap := generic.NewPathFromString("/O-A/O-J/O-M")
mappedID := id.NewNodeID("MAPPED")
aTree.Find(pathToMap).SetMappedID(mappedID)
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
bNode := bTree.Find(pathToMap).Delete(ctx)
bNode.SetID(mappedID)
parentPathToMap := generic.NewPathFromString("/O-A/O-J")
bTree.Find(parentPathToMap).SetChild(bNode)
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
}
}

View file

@ -0,0 +1,193 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"sort"
"testing"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/path"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/tree/memory"
"github.com/stretchr/testify/assert"
)
func NewMemoryTree(ctx context.Context, name string) generic.TreeInterface {
return generic.GetFactory("memory")(ctx, memory.NewOptions(memory.NewIDAllocatorGenerator(name)))
}
func TestMemoryTreeIDAllocator(t *testing.T) {
ctx := context.Background()
name := "T"
id := "THEID"
for _, testCase := range []struct {
idAllocator memory.IDAllocatorInterface
setID bool
expectedID string
}{
{
idAllocator: memory.NewIDAllocatorGenerator(name),
setID: false,
expectedID: name + "-A",
},
{
idAllocator: memory.NewIDAllocatorNull(),
setID: true,
expectedID: id,
},
} {
t.Run(testCase.expectedID, func(t *testing.T) {
tree := generic.GetFactory("memory")(ctx, memory.NewOptions(testCase.idAllocator))
node := tree.GetRoot().CreateChild(ctx)
f := memory.NewFormat("")
if testCase.setID {
f.SetID(id)
}
node.FromFormat(f)
node.Upsert(ctx)
assert.EqualValues(t, testCase.expectedID, node.GetID())
})
}
}
func testTreeBuild(t *testing.T, tree generic.TreeInterface, maxDepth int) {
ctx := context.Background()
insert := func(tree generic.TreeInterface, parent generic.NodeInterface) generic.NodeInterface {
node := parent.CreateChild(ctx)
node.Upsert(ctx)
memory.SetContent(node, "content "+node.GetID().String())
node.Upsert(ctx)
return node
}
var populate func(depth int, tree generic.TreeInterface, parent generic.NodeInterface)
populate = func(depth int, tree generic.TreeInterface, parent generic.NodeInterface) {
if depth >= maxDepth {
return
}
depth++
for i := 1; i <= 3; i++ {
node := insert(tree, parent)
populate(depth, tree, node)
}
}
populate(0, tree, insert(tree, tree.GetRoot()))
}
func TestMemoryTreeBuild(t *testing.T) {
ctx := context.Background()
verify := func(tree generic.TreeInterface, expected []string) {
collected := make([]string, 0, 10)
collect := func(ctx context.Context, p path.Path, node generic.NodeInterface) {
if node.GetKind() == kind.KindRoot {
return
}
p = p.Append(node)
collected = append(collected, p.String()+":"+memory.GetContent(node))
}
tree.WalkAndGet(ctx, generic.NewWalkOptions(collect))
sort.Strings(expected)
sort.Strings(collected)
assert.EqualValues(t, expected, collected)
}
for _, testCase := range []struct {
name string
build func(tree generic.TreeInterface)
operations func(tree generic.TreeInterface)
expected []string
}{
{
name: "full tree",
build: func(tree generic.TreeInterface) { testTreeBuild(t, tree, 2) },
operations: func(tree generic.TreeInterface) {},
expected: []string{"/T-A/T-B/T-C:content T-C", "/T-A/T-B/T-D:content T-D", "/T-A/T-B/T-E:content T-E", "/T-A/T-B:content T-B", "/T-A/T-F/T-G:content T-G", "/T-A/T-F/T-H:content T-H", "/T-A/T-F/T-I:content T-I", "/T-A/T-F:content T-F", "/T-A/T-J/T-K:content T-K", "/T-A/T-J/T-L:content T-L", "/T-A/T-J/T-M:content T-M", "/T-A/T-J:content T-J", "/T-A:content T-A"},
},
{
name: "scenario 1",
build: func(tree generic.TreeInterface) { testTreeBuild(t, tree, 2) },
operations: func(tree generic.TreeInterface) {
root := tree.GetRoot()
id0 := id.NewNodeID("T-A")
root.List(ctx)
zero := root.GetChild(id0)
assert.False(t, generic.NilNode == zero)
assert.True(t, zero == zero.Get(ctx))
id1 := id.NewNodeID("T-B")
zero.List(ctx)
one := zero.GetChild(id1)
assert.False(t, generic.NilNode == one)
one.Get(ctx)
memory.SetContent(one, "other one")
one.Upsert(ctx)
id2 := id.NewNodeID("T-F")
two := zero.GetChild(id2)
two.Delete(ctx)
two.Delete(ctx)
assert.True(t, generic.NilNode == zero.GetChild(id2))
},
expected: []string{"/T-A/T-B/T-C:content T-C", "/T-A/T-B/T-D:content T-D", "/T-A/T-B/T-E:content T-E", "/T-A/T-B:other one", "/T-A/T-J/T-K:content T-K", "/T-A/T-J/T-L:content T-L", "/T-A/T-J/T-M:content T-M", "/T-A/T-J:content T-J", "/T-A:content T-A"},
},
{
name: "scenario 2",
build: func(tree generic.TreeInterface) { testTreeBuild(t, tree, 0) },
operations: func(tree generic.TreeInterface) {
root := tree.GetRoot()
id0 := id.NewNodeID("T-A")
root.List(ctx)
zero := root.GetChild(id0)
assert.False(t, generic.NilNode == zero)
zero.Get(ctx)
one := zero.CreateChild(ctx)
one.Upsert(ctx)
memory.SetContent(one, "ONE")
one.Upsert(ctx)
two := one.CreateChild(ctx)
two.Upsert(ctx)
memory.SetContent(two, "SOMETHING")
two.Upsert(ctx)
memory.SetContent(two, "ONE/TWO")
two.Upsert(ctx)
one.DeleteChild(two.GetID())
two.Get(ctx)
three := two.CreateChild(ctx)
three.Upsert(ctx)
memory.SetContent(three, "ONE/THREE")
three.Upsert(ctx)
three.Delete(ctx)
},
expected: []string{"/T-A/T-B/T-C:ONE/TWO", "/T-A/T-B:ONE", "/T-A:content T-A"},
},
} {
t.Run(testCase.name, func(t *testing.T) {
tree := NewMemoryTree(ctx, "T")
tree.Trace("========== BUILD")
testCase.build(tree)
tree.Trace("========== OPERATIONS")
testCase.operations(tree)
verify(tree, testCase.expected)
tree.Trace("========== VERIFY RELOAD")
tree.Clear(ctx)
verify(tree, testCase.expected)
})
}
}

View file

@ -0,0 +1,98 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"testing"
"code.forgejo.org/f3/gof3/v3/path"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/tree/memory"
"github.com/stretchr/testify/assert"
)
type testReference struct {
originPath string
originReference string
destinationPath string
destinationReference string
}
func TestMirror(t *testing.T) {
ctx := context.Background()
for _, testCase := range []struct {
start string
references []testReference
expected []string
}{
{
start: "/",
expected: []string{":ROOT:", "/D-A:content O-A:", "/D-A/D-B:content O-B:", "/D-A/D-B/D-C:content O-C:", "/D-A/D-B/D-D:content O-D:", "/D-A/D-B/D-E:content O-E:", "/D-A/D-F:content O-F:", "/D-A/D-F/D-G:content O-G:", "/D-A/D-F/D-H:content O-H:", "/D-A/D-F/D-I:content O-I:", "/D-A/D-J:content O-J:", "/D-A/D-J/D-K:content O-K:", "/D-A/D-J/D-L:content O-L:", "/D-A/D-J/D-M:content O-M:"},
},
{
start: "/O-A/O-B",
references: []testReference{
{
originPath: "/O-A/O-B/O-C",
originReference: "/O-A/O-F",
destinationPath: "/D-A/D-B/D-D",
destinationReference: "/D-A/D-C",
},
},
expected: []string{":ROOT:", "/D-A:content O-A:", "/D-A/D-B:content O-B:", "/D-A/D-B/D-D:content O-C:/D-A/D-C", "/D-A/D-B/D-E:content O-D:", "/D-A/D-B/D-F:content O-E:", "/D-A/D-C:content O-F:"},
},
{
start: "/O-A/O-F",
references: []testReference{
{
originPath: "/O-A/O-F/O-G",
originReference: "../../O-J",
destinationPath: "/D-A/D-B/D-D",
destinationReference: "../../D-C",
},
},
expected: []string{":ROOT:", "/D-A:content O-A:", "/D-A/D-B:content O-F:", "/D-A/D-B/D-D:content O-G:../../D-C", "/D-A/D-B/D-E:content O-H:", "/D-A/D-B/D-F:content O-I:", "/D-A/D-C:content O-J:"},
},
} {
t.Run(" "+testCase.start, func(t *testing.T) {
originTree := NewMemoryTree(ctx, "O")
log := originTree.GetLogger()
log.Trace("=========== build")
testTreeBuild(t, originTree, 2)
for _, c := range testCase.references {
log.Trace("=========== inject reference %s", c.originReference)
assert.True(t, originTree.Apply(ctx, generic.NewPathFromString(c.originPath), generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
memory.SetRef(node, c.originReference)
})))
}
log.Trace("=========== mirror")
destinationTree := NewMemoryTree(ctx, "D")
generic.TreeMirror(ctx, originTree, destinationTree, generic.NewPathFromString(testCase.start), generic.NewMirrorOptions())
log.Trace("=========== verify")
collected := make([]string, 0, 10)
collect := func(ctx context.Context, parent path.Path, node generic.NodeInterface) {
collected = append(collected, node.GetCurrentPath().String()+":"+memory.GetContent(node)+":"+memory.GetRef(node))
}
destinationTree.Walk(ctx, generic.NewWalkOptions(collect))
assert.EqualValues(t, testCase.expected, collected)
for _, c := range testCase.references {
log.Trace("=========== look for reference %s", c.destinationReference)
var called bool
assert.True(t, destinationTree.Apply(ctx, generic.NewPathFromString(c.destinationPath), generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
assert.EqualValues(t, c.destinationReference, memory.GetRef(node))
called = true
})))
assert.True(t, called)
}
})
}
}

View file

@ -0,0 +1,341 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"fmt"
"sort"
"testing"
"code.forgejo.org/f3/gof3/v3/path"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/tree/memory"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBothApplyWalk(t *testing.T) {
ctx := context.Background()
tree := NewMemoryTree(ctx, "T")
for _, testCase := range []struct {
path string
expected []string
}{
{
path: "/T-A",
expected: []string{"/T-A", "/T-A/T-B", "/T-A/T-B/T-C", "/T-A/T-B/T-D", "/T-A/T-B/T-E", "/T-A/T-F", "/T-A/T-F/T-G", "/T-A/T-F/T-H", "/T-A/T-F/T-I", "/T-A/T-J", "/T-A/T-J/T-K", "/T-A/T-J/T-L", "/T-A/T-J/T-M"},
},
{
path: "/T-A/T-B",
expected: []string{"/T-A/T-B", "/T-A/T-B/T-C", "/T-A/T-B/T-D", "/T-A/T-B/T-E"},
},
{
path: "/T-A/T-B/T-C",
expected: []string{"/T-A/T-B/T-C"},
},
} {
testTreeBuild(t, tree, 2)
collected := make([]string, 0, 10)
p := generic.NewPathFromString(testCase.path)
walk := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
collect := func(ctx context.Context, p path.Path, node generic.NodeInterface) {
p = p.Append(node)
collected = append(collected, p.String())
}
node.Walk(ctx, parent, generic.NewWalkOptions(collect))
}
assert.True(t, tree.Apply(ctx, p, generic.NewApplyOptions(walk)))
sort.Strings(testCase.expected)
sort.Strings(collected)
assert.EqualValues(t, testCase.expected, collected)
}
}
func TestWalk(t *testing.T) {
ctx := context.Background()
tree := NewMemoryTree(ctx, "T")
expected := []string{"", "/T-A", "/T-A/T-B", "/T-A/T-B/T-C", "/T-A/T-B/T-D", "/T-A/T-B/T-E", "/T-A/T-F", "/T-A/T-F/T-G", "/T-A/T-F/T-H", "/T-A/T-F/T-I", "/T-A/T-J", "/T-A/T-J/T-K", "/T-A/T-J/T-L", "/T-A/T-J/T-M"}
testTreeBuild(t, tree, 2)
collected := make([]string, 0, 10)
collect := func(ctx context.Context, p path.Path, node generic.NodeInterface) {
p = p.Append(node)
collected = append(collected, p.String())
}
tree.Walk(ctx, generic.NewWalkOptions(collect))
sort.Strings(expected)
sort.Strings(collected)
assert.EqualValues(t, expected, collected)
}
func TestWalkAndGet(t *testing.T) {
ctx := context.Background()
tree := NewMemoryTree(ctx, "T")
testTreeBuild(t, tree, 1)
tree.Clear(ctx)
collected := make([]string, 0, 10)
collect := func(ctx context.Context, p path.Path, node generic.NodeInterface) {
p = p.Append(node)
collected = append(collected, p.String())
}
tree.Walk(ctx, generic.NewWalkOptions(collect))
assert.EqualValues(t, []string{""}, collected)
collected = make([]string, 0, 10)
tree.WalkAndGet(ctx, generic.NewWalkOptions(collect))
expected := []string{"", "/T-A", "/T-A/T-B", "/T-A/T-C", "/T-A/T-D"}
sort.Strings(expected)
sort.Strings(collected)
assert.EqualValues(t, expected, collected)
}
func TestApplyVisitID(t *testing.T) {
ctx := context.Background()
tree := NewMemoryTree(ctx, "T")
testTreeBuild(t, tree, 2)
for _, testPath := range []string{"/T-A", "/T-A/T-B", "/T-A/T-B/T-C"} {
collected := make([]string, 0, 10)
p := generic.NewPathFromString(testPath)
collect := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
parent = parent.Append(node)
assert.False(t, node.GetIsNil(), node.String())
assert.EqualValues(t, parent.Length(), node.GetCurrentPath().Length())
expected := parent.PathString().Join()
actual := node.GetCurrentPath().String()
assert.EqualValues(t, actual, expected)
collected = append(collected, parent.String())
}
assert.True(t, tree.Apply(ctx, p, generic.NewApplyOptions(collect)))
if assert.EqualValues(t, 1, len(collected)) {
assert.EqualValues(t, testPath, collected[0])
}
}
p := generic.NewPathFromString("/1/2/3/4")
called := false
assert.False(t, tree.Apply(ctx, p, generic.NewApplyOptions(func(context.Context, path.Path, path.Path, generic.NodeInterface) { called = true })))
assert.False(t, called)
}
func TestApplyVisitByName(t *testing.T) {
ctx := context.Background()
tree := NewMemoryTree(ctx, "T")
testTreeBuild(t, tree, 2)
for _, testCase := range []struct {
path string
expected string
}{
{
path: "/T-A/content T-B/T-C",
expected: "/T-A/T-B/T-C",
},
{
path: "/T-A/content T-B/content T-C",
expected: "/T-A/T-B/T-C",
},
{
path: "/content T-A/content T-B/content T-C",
expected: "/T-A/T-B/T-C",
},
} {
collected := make([]string, 0, 10)
p := generic.NewPathFromString(testCase.path)
collect := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
parent = parent.Append(node)
assert.False(t, node.GetIsNil(), node.String())
assert.EqualValues(t, parent.Length(), node.GetCurrentPath().Length())
expected := parent.PathString().Join()
actual := node.GetCurrentPath().String()
assert.EqualValues(t, actual, expected)
collected = append(collected, parent.String())
}
assert.True(t, tree.Apply(ctx, p, generic.NewApplyOptions(collect).SetSearch(generic.ApplySearchByName)))
if assert.EqualValues(t, 1, len(collected)) {
assert.EqualValues(t, testCase.expected, collected[0])
}
}
}
func TestApplyAndGet(t *testing.T) {
ctx := context.Background()
for _, testCase := range []struct {
path string
expected []string
}{
{
path: "/T-A",
expected: []string{"", "/T-A"},
},
{
path: "/T-A/T-B",
expected: []string{"", "/T-A", "/T-A/T-B"},
},
{
path: "/T-A/T-B/T-C",
expected: []string{"", "/T-A", "/T-A/T-B", "/T-A/T-B/T-C"},
},
} {
tree := NewMemoryTree(ctx, "T")
testTreeBuild(t, tree, 2)
tree.Clear(ctx)
p := generic.NewPathFromString(testCase.path)
var collected []string
collect := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
parent = parent.Append(node)
collected = append(collected, parent.String())
}
{
collected = make([]string, 0, 10)
require.False(t, tree.Apply(ctx, p, generic.NewApplyOptions(collect)))
}
{
collected = make([]string, 0, 10)
require.True(t, tree.ApplyAndGet(ctx, p, generic.NewApplyOptions(collect)))
require.EqualValues(t, 1, len(collected))
assert.EqualValues(t, testCase.path, collected[0])
}
{
collected = make([]string, 0, 10)
require.True(t, tree.ApplyAndGet(ctx, p, generic.NewApplyOptions(collect).SetWhere(generic.ApplyEachNode)))
sort.Strings(testCase.expected)
sort.Strings(collected)
assert.EqualValues(t, testCase.expected, collected)
}
}
}
func TestApplyVisitRelative(t *testing.T) {
ctx := context.Background()
tree := NewMemoryTree(ctx, "T")
// The "destination" is clean and there is no need to test a/../b which becomes
// b etc. Only when the first element of the path is either an id or ..
for _, testCase := range []struct {
start string
destination string
expected string
}{
{
start: "/T-A",
destination: "T-B",
expected: "/T-A/T-B",
},
{
start: "/T-A/T-B",
destination: ".",
expected: "/T-A/T-B",
},
{
start: "/T-A/T-B",
destination: "T-C",
expected: "/T-A/T-B/T-C",
},
{
start: "/T-A/T-B",
destination: "..",
expected: "/T-A",
},
{
start: "/T-A/T-B",
destination: "../T-F/T-G",
expected: "/T-A/T-F/T-G",
},
{
start: "/T-A/T-B/T-C",
destination: "../../T-F",
expected: "/T-A/T-F",
},
} {
t.Run(" "+testCase.start+" => "+testCase.destination, func(t *testing.T) {
testTreeBuild(t, tree, 2)
collected := make([]string, 0, 10)
start := generic.NewPathFromString(testCase.start)
collect := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
parent = parent.Append(node)
assert.False(t, node.GetIsNil(), node.String())
assert.EqualValues(t, parent.Length(), node.GetCurrentPath().Length())
expected := parent.PathString().Join()
actual := node.GetCurrentPath().String()
assert.EqualValues(t, actual, expected)
collected = append(collected, parent.String())
}
cd := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
destination := generic.NewPathFromString(testCase.destination)
fmt.Println("start ", node.GetCurrentPath().String())
assert.True(t, node.Apply(ctx, parent, destination, generic.NewApplyOptions(collect)))
}
assert.True(t, tree.Apply(ctx, start, generic.NewApplyOptions(cd)))
if assert.EqualValues(t, 1, len(collected)) {
assert.EqualValues(t, testCase.expected, collected[0])
}
})
p := generic.NewPathFromString("/1/2/3/4")
called := false
assert.False(t, tree.Apply(ctx, p, generic.NewApplyOptions(func(context.Context, path.Path, path.Path, generic.NodeInterface) { called = true })))
assert.False(t, called)
}
}
func TestApplyUpsert(t *testing.T) {
ctx := context.Background()
tree := NewMemoryTree(ctx, "T")
testTreeBuild(t, tree, 2)
expected := generic.NewPathFromString("/T-A/T-B/T-N")
assert.False(t, tree.Exists(ctx, expected))
assert.True(t, tree.Apply(ctx, generic.NewPathFromString("/T-A/T-B"), generic.NewApplyOptions(func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
new := node.CreateChild(ctx)
new.Upsert(ctx)
memory.SetContent(new, "content "+new.GetID().String())
new.Upsert(ctx)
})))
assert.True(t, tree.Exists(ctx, expected))
}
func TestApplyDelete(t *testing.T) {
ctx := context.Background()
tree := NewMemoryTree(ctx, "T")
testTreeBuild(t, tree, 2)
toDelete := generic.NewPathFromString("/T-A/T-B")
assert.True(t, tree.Exists(ctx, toDelete))
assert.True(t, tree.Apply(ctx, toDelete, generic.NewApplyOptions(func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
node.Delete(ctx)
})))
assert.False(t, tree.Exists(ctx, toDelete))
}

View file

@ -0,0 +1,74 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"sort"
"testing"
"code.forgejo.org/f3/gof3/v3/path"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/tree/memory"
"github.com/stretchr/testify/assert"
)
func TestTreeCollectReferences(t *testing.T) {
ctx := context.Background()
for _, testCase := range []struct {
path string
expected []string
}{
{
path: "/O-A/O-B",
expected: []string{"/O-A/O-B/O-C"},
},
{
path: "/O-A/O-D",
expected: []string{},
},
{
path: "/O-A/O-J",
expected: []string{"/O-A/O-F", "/O-A/O-J/O-K"},
},
{
path: "/O-A/O-J/O-L",
expected: []string{"/O-A/O-J/O-K"},
},
{
path: "/O-A/O-J/O-M",
expected: []string{"/O-A/O-F"},
},
{
path: "/O-A",
expected: []string{"/O-A/O-B/O-C", "/O-A/O-F", "/O-A/O-F/O-H", "/O-A/O-J/O-K"},
},
} {
t.Run(testCase.path, func(t *testing.T) {
tree := NewMemoryTree(ctx, "O")
testTreeBuild(t, tree, 2)
setReference := func(p, reference string) {
assert.True(t, tree.Apply(ctx, generic.NewPathFromString(p), generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
memory.SetRef(node, reference)
})))
}
setReference("/O-A/O-B", "/O-A/O-B/O-C")
setReference("/O-A/O-F/O-G", "/O-A/O-F/O-H")
setReference("/O-A/O-J/O-M", "../../O-F")
setReference("/O-A/O-J/O-L", "../O-K")
actual := make([]string, 0, 10)
for _, reference := range generic.TreeCollectReferences(ctx, tree, generic.NewPathFromString(testCase.path)) {
actual = append(actual, reference.String())
}
sort.Strings(actual)
assert.EqualValues(t, testCase.expected, actual)
})
}
}

View file

@ -0,0 +1,626 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"fmt"
"path/filepath"
"sort"
"testing"
"code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/path"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/tree/memory"
"github.com/stretchr/testify/assert"
)
func TestUnifyPathSimpleRemap(t *testing.T) {
ctx := context.Background()
for _, testCase := range []struct {
path string
expected []string
}{
{
path: "",
expected: []string{},
},
{
path: "/O-A",
expected: []string{"/O-A:O-A=content O-A => /D-A:D-A=content O-A"},
},
{
path: "/O-A/O-B",
expected: []string{"/O-A:O-A=content O-A => /D-A:D-A=content O-A", "/O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B"},
},
{
path: "/O-A/O-B/O-C",
expected: []string{"/O-A:O-A=content O-A => /D-A:D-A=content O-A", "/O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "/O-A/O-B/O-C:O-C=content O-C => /D-A/D-B/D-C:D-C=content O-C"},
},
} {
t.Run(" "+testCase.path, func(t *testing.T) {
originTree := NewMemoryTree(ctx, "O")
testTreeBuild(t, originTree, 2)
destinationTree := NewMemoryTree(ctx, "D")
collected := make([]string, 0, 10)
p := generic.NewPathFromString(testCase.path)
upsert := func(ctx context.Context, origin generic.NodeInterface, originPath path.Path, destination generic.NodeInterface, destinationPath path.Path) {
fmt.Printf("origin %v destination %v\n", origin, destination)
originPath = originPath.Append(origin)
destinationPath = destinationPath.Append(destination)
collected = append(collected, originPath.String()+":"+origin.String()+" => "+destinationPath.String()+":"+destination.String())
}
generic.TreeUnifyPath(ctx, originTree, p, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
assert.EqualValues(t, testCase.expected, collected)
collected = make([]string, 0, 10)
generic.TreeUnifyPath(ctx, originTree, p, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
assert.EqualValues(t, testCase.expected, collected)
})
}
}
func TestUnifyPathSimpleNoRemap(t *testing.T) {
ctx := context.Background()
for _, testCase := range []struct {
noremap bool
path string
expected []string
}{
{
noremap: false,
path: "/O-A/O-B",
expected: []string{"/O-A:O-A=content O-A => /D-A:D-A=content O-A", "/O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B"},
},
{
noremap: true,
path: "/O-A/O-B",
expected: []string{"/O-A:O-A=content O-A => /O-A:O-A=content O-A", "/O-A/O-B:O-B=content O-B => /O-A/O-B:O-B=content O-B"},
},
} {
t.Run(fmt.Sprintf("noremap=%v,path=%s", testCase.noremap, testCase.path), func(t *testing.T) {
originName := "O"
originTree := NewMemoryTree(ctx, originName)
testTreeBuild(t, originTree, 2)
var destinationName string
if testCase.noremap {
destinationName = originName
} else {
destinationName = "D"
}
destinationTree := NewMemoryTree(ctx, destinationName)
collected := make([]string, 0, 10)
p := generic.NewPathFromString(testCase.path)
upsert := func(ctx context.Context, origin generic.NodeInterface, originPath path.Path, destination generic.NodeInterface, destinationPath path.Path) {
fmt.Printf("origin %v destination %v\n", origin, destination)
originPath = originPath.Append(origin)
destinationPath = destinationPath.Append(destination)
collected = append(collected, originPath.String()+":"+origin.String()+" => "+destinationPath.String()+":"+destination.String())
}
generic.TreeUnifyPath(ctx, originTree, p, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert).SetNoRemap(testCase.noremap))
assert.EqualValues(t, testCase.expected, collected)
collected = make([]string, 0, 10)
generic.TreeUnifyPath(ctx, originTree, p, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert).SetNoRemap(testCase.noremap))
assert.EqualValues(t, testCase.expected, collected)
})
}
}
func TestUnifyPathRelative(t *testing.T) {
ctx := context.Background()
for _, testCase := range []struct {
start string
destination string
expected []string
}{
{
start: "/O-A/O-B",
destination: "O-C",
expected: []string{"cd: /O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "unify: /O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "unify: /O-A/O-B/O-C:O-C=content O-C => /D-A/D-B/D-C:D-C=content O-C"},
},
{
start: "/O-A/O-B",
destination: ".",
expected: []string{"cd: /O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "unify: /O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B"},
},
{
start: "/O-A/O-B",
destination: "../O-F/O-G",
expected: []string{"cd: /O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "unify: /O-A:O-A=content O-A => /D-A:D-A=content O-A", "unify: /O-A/O-F:O-F=content O-F => /D-A/D-C:D-C=content O-F", "unify: /O-A/O-F/O-G:O-G=content O-G => /D-A/D-C/D-D:D-D=content O-G"},
},
{
start: "/O-A/O-B/O-C",
destination: "../O-E",
expected: []string{"cd: /O-A/O-B/O-C:O-C=content O-C => /D-A/D-B/D-C:D-C=content O-C", "unify: /O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "unify: /O-A/O-B/O-E:O-E=content O-E => /D-A/D-B/D-D:D-D=content O-E"},
},
} {
t.Run(" "+testCase.start+" => "+testCase.destination, func(t *testing.T) {
originTree := NewMemoryTree(ctx, "O")
testTreeBuild(t, originTree, 2)
destinationTree := NewMemoryTree(ctx, "D")
var collected []string
start := generic.NewPathFromString(testCase.start)
collect := func(prefix string, origin, destination generic.NodeInterface) {
originPath := origin.GetCurrentPath().String()
destinationPath := destination.GetCurrentPath().String()
collected = append(collected, prefix+originPath+":"+origin.GetSelf().String()+" => "+destinationPath+":"+destination.GetSelf().String())
}
//
// Unify testCase.start
//
upsert := func(ctx context.Context, origin generic.NodeInterface, originParent path.Path, destination generic.NodeInterface, destinationParent path.Path) {
collect("unify: ", origin, destination)
}
generic.TreeUnifyPath(ctx, originTree, start, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
//
// Beginning from testCase.start, unify testCase.destination
//
cd := func(ctx context.Context, origin, destination generic.NodeInterface) {
collect("cd: ", origin, destination)
path := generic.NewPathFromString(testCase.destination)
originParent := origin.GetParent().GetCurrentPath()
destinationParent := destination.GetParent().GetCurrentPath()
generic.NodeUnifyPath(ctx, origin.GetSelf(), originParent, path, destinationParent, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
}
//
//
//
collected = make([]string, 0, 10)
generic.TreeParallelApply(ctx, originTree, start, destinationTree, generic.NewParallelApplyOptions(cd))
assert.EqualValues(t, testCase.expected, collected)
//
// Do it twice to verify it is idempotent
//
collected = make([]string, 0, 10)
generic.TreeParallelApply(ctx, originTree, start, destinationTree, generic.NewParallelApplyOptions(cd))
assert.EqualValues(t, testCase.expected, collected)
})
}
}
func TestUnifyPathScenario(t *testing.T) {
ctx := context.Background()
//
// build and populate a tree
//
originTree := NewMemoryTree(ctx, "O")
testTreeBuild(t, originTree, 2)
//
// build an empty tree
//
destinationTree := NewMemoryTree(ctx, "D")
//
// accumulate the call results for verification
//
var collected []string
upsert := func(ctx context.Context, origin generic.NodeInterface, originPath path.Path, destination generic.NodeInterface, destinationPath path.Path) {
originPath = originPath.Append(origin)
destinationPath = destinationPath.Append(destination)
what := originPath.String() + ":" + origin.String() + " => " + destinationPath.String() + ":" + destination.String()
fmt.Printf("unify: %T => %T | %s\n", origin, destination, what)
collected = append(collected, what)
}
assertTree := func(tree generic.TreeInterface, expected []string) {
collected := make([]string, 0, 10)
tree.Walk(ctx, generic.NewWalkOptions(func(ctx context.Context, path path.Path, node generic.NodeInterface) {
if node.GetKind() == kind.KindRoot {
return
}
path = path.Append(node)
collected = append(collected, path.String()+":"+node.String())
}))
sort.Strings(collected)
assert.EqualValues(t, expected, collected)
}
//
// unify the originTree with the destinationTree on the specified path
//
fullPath := generic.NewPathFromString("/O-A/O-B/O-C")
collected = make([]string, 0, 10)
generic.TreeUnifyPath(ctx, originTree, fullPath, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
sort.Strings(collected)
assert.EqualValues(t, []string{"/O-A/O-B/O-C:O-C=content O-C => /D-A/D-B/D-C:D-C=content O-C", "/O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "/O-A:O-A=content O-A => /D-A:D-A=content O-A"}, collected)
assertTree(destinationTree, []string{"/D-A/D-B/D-C:D-C=content O-C", "/D-A/D-B:D-B=content O-B", "/D-A:D-A=content O-A"})
//
// Add a node unrelated to the unification path
//
var unrelatedOriginPath path.Path
{
originTree.Apply(ctx, generic.NewPathFromString("/O-A/O-B"), generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
assert.EqualValues(t, parent.Length()+1, node.GetCurrentPath().Length())
unrelated := node.CreateChild(ctx)
unrelated.Upsert(ctx)
memory.SetContent(unrelated, "content "+unrelated.GetID().String())
unrelated.Upsert(ctx)
unrelatedOriginPath = unrelated.GetCurrentPath()
assert.EqualValues(t, "/O-A/O-B/O-N", (unrelatedOriginPath.PathString().Join()))
}))
}
//
// Replace the content of the last node
//
lastContent := "LAST"
{
lastPath := generic.NewPathFromString("/O-A/O-B/O-C")
originTree.Apply(ctx, lastPath, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
memory.SetContent(node, lastContent)
}))
collected = make([]string, 0, 10)
generic.TreeUnifyPath(ctx, originTree, lastPath, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
sort.Strings(collected)
assert.EqualValues(t, []string{"/O-A/O-B/O-C:O-C=LAST => /D-A/D-B/D-C:D-C=LAST", "/O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "/O-A:O-A=content O-A => /D-A:D-A=content O-A"}, collected)
assertTree(destinationTree, []string{"/D-A/D-B/D-C:D-C=LAST", "/D-A/D-B:D-B=content O-B", "/D-A:D-A=content O-A"})
}
//
// Replace the content of the first node
//
firstContent := "FIRST"
{
firstPath := generic.NewPathFromString("/O-A")
originTree.Apply(ctx, firstPath, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
memory.SetContent(node, firstContent)
}))
collected = make([]string, 0, 10)
generic.TreeUnifyPath(ctx, originTree, firstPath, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
sort.Strings(collected)
assert.EqualValues(t, []string{"/O-A:O-A=" + firstContent + " => /D-A:D-A=" + firstContent}, collected)
assertTree(destinationTree, []string{"/D-A/D-B/D-C:D-C=LAST", "/D-A/D-B:D-B=content O-B", "/D-A:D-A=FIRST"})
}
//
// Replace the content of the second node
//
secondContent := "SECOND"
{
secondPath := generic.NewPathFromString("/O-A/O-B")
originTree.Apply(ctx, secondPath, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
memory.SetContent(node, secondContent)
}))
collected = make([]string, 0, 10)
generic.TreeUnifyPath(ctx, originTree, secondPath, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
assert.EqualValues(t, []string{"/O-A:O-A=" + firstContent + " => /D-A:D-A=" + firstContent, "/O-A/O-B:O-B=" + secondContent + " => /D-A/D-B:D-B=" + secondContent}, collected)
sort.Strings(collected)
assertTree(destinationTree, []string{"/D-A/D-B/D-C:D-C=LAST", "/D-A/D-B:D-B=SECOND", "/D-A:D-A=FIRST"})
}
//
// verify the node unrelated to the unification is still there
//
{
var found bool
originTree.Apply(ctx, unrelatedOriginPath, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
found = true
}))
assert.True(t, found)
}
}
func TestUnifyMirror(t *testing.T) {
ctx := context.Background()
//
// build and populate a tree
//
originTree := NewMemoryTree(ctx, "O")
log := originTree.GetLogger()
testTreeBuild(t, originTree, 2)
//
// build an empty tree
//
destinationTree := NewMemoryTree(ctx, "D")
upsert := func(ctx context.Context, origin generic.NodeInterface, originPath path.Path, destination generic.NodeInterface, destinationPath path.Path) {
assert.NotNil(t, origin.GetDriver().(*memory.Driver))
assert.NotNil(t, destination.GetDriver().(*memory.Driver))
originPath = originPath.Append(origin)
destinationPath = destinationPath.Append(destination)
what := fmt.Sprintf("%s:%s => %s:%s", originPath, origin.GetSelf(), destinationPath, destination.GetSelf())
log.Trace("mirror upsert: %T => %T | %s", origin, destination, what)
}
delete := func(ctx context.Context, destination generic.NodeInterface, destinationPath path.Path) {
assert.NotNil(t, destination.GetDriver().(*memory.Driver))
destinationPath = destinationPath.Append(destination)
log.Trace("mirror delete: %T | %s:%s", destination, destinationPath, destination)
}
var sameTree func(origin, destination generic.NodeInterface) bool
sameTree = func(origin, destination generic.NodeInterface) bool {
what := origin.GetCurrentPath().String() + ":" + origin.GetSelf().String() + " => " + destination.GetCurrentPath().String() + ":" + destination.GetSelf().String()
log.Trace("sameTree: %T => %T | %s", origin.GetSelf(), destination.GetSelf(), what)
if origin.GetMappedID() != destination.GetID() {
log.Trace("sameTree: different: %s != %s", origin.GetMappedID(), destination.GetID())
return false
}
originChildren := origin.GetChildren()
destinationChildren := destination.GetChildren()
if len(originChildren) != len(destinationChildren) {
log.Trace("sameTree: different: length %v != %v", len(originChildren), len(destinationChildren))
return false
}
for _, originChild := range originChildren {
destinationChild := destination.GetChild(originChild.GetMappedID())
if destinationChild == generic.NilNode {
log.Trace("sameTree: different: %s not found", originChild.GetMappedID())
return false
}
if !sameTree(originChild, destinationChild) {
return false
}
}
return true
}
//
// unify the originTree with the destinationTree
//
assert.False(t, sameTree(originTree.GetRoot(), destinationTree.GetRoot()))
generic.TreeUnify(ctx, originTree, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert).SetDelete(delete))
assert.True(t, sameTree(originTree.GetRoot(), destinationTree.GetRoot()))
generic.TreeUnify(ctx, originTree, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert).SetDelete(delete))
assert.True(t, sameTree(originTree.GetRoot(), destinationTree.GetRoot()))
{
addNode := func(tree generic.TreeInterface, pathString string) {
tree.Apply(ctx, generic.NewPathFromString(pathString), generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
new := node.CreateChild(ctx)
new.Upsert(ctx)
memory.SetContent(new, "content "+new.GetID().String())
new.Upsert(ctx)
log.Trace("add: %s", parent.ReadableString())
log.Trace("add: %s", node.GetCurrentPath().ReadableString())
log.Trace("add: %s", new.GetCurrentPath().ReadableString())
}))
}
for _, testCase := range []struct {
existingPath string
newPath string
}{
{
existingPath: "/O-A/O-B",
newPath: "/D-A/D-B/D-N",
},
{
existingPath: "/O-A",
newPath: "/D-A/D-O",
},
{
existingPath: "/O-A/O-J/O-K",
newPath: "/D-A/D-J/D-K/D-P",
},
} {
t.Run("add"+testCase.newPath, func(t *testing.T) {
destinationPath := generic.NewPathFromString(testCase.newPath)
addNode(originTree, testCase.existingPath)
assert.False(t, sameTree(originTree.GetRoot(), destinationTree.GetRoot()))
assert.False(t, destinationTree.Exists(ctx, destinationPath), destinationPath.String())
generic.TreeUnify(ctx, originTree, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert).SetDelete(delete))
assert.True(t, destinationTree.Exists(ctx, destinationPath), destinationPath.String())
assert.True(t, sameTree(originTree.GetRoot(), destinationTree.GetRoot()))
})
}
}
{
deleteNode := func(tree generic.TreeInterface, toDelete path.Path) {
tree.Apply(ctx, toDelete, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
node.Delete(ctx)
}))
}
for _, testCase := range []struct {
originPath string
destinationPath string
}{
{
originPath: "/O-A/O-F",
destinationPath: "/D-A/D-F",
},
} {
t.Run("delete"+testCase.originPath, func(t *testing.T) {
originPath := generic.NewPathFromString(testCase.originPath)
destinationPath := generic.NewPathFromString(testCase.destinationPath)
assert.True(t, originTree.Exists(ctx, originPath))
assert.True(t, destinationTree.Exists(ctx, destinationPath), destinationPath.String())
deleteNode(originTree, originPath)
assert.False(t, originTree.Exists(ctx, originPath))
generic.TreeUnify(ctx, originTree, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert).SetDelete(delete))
assert.False(t, destinationTree.Exists(ctx, destinationPath), destinationPath.String())
})
}
}
}
func TestNodeParallelApplyFound(t *testing.T) {
ctx := context.Background()
for _, testCase := range []struct {
start string
destination string
expected []string
expectedRemapped string
}{
{
start: "/O-A/O-B",
destination: "O-C",
expected: []string{"/O-A/O-B => /D-A/D-B", "/O-A/O-B/O-C => /D-A/D-B/D-C"},
expectedRemapped: "/D-A/D-B/D-C",
},
{
start: "/O-A/O-B/O-D",
destination: ".",
expected: []string{"/O-A/O-B/O-D => /D-A/D-B/D-D"},
expectedRemapped: "/D-A/D-B/D-D",
},
{
start: ".",
destination: ".",
expected: []string{" => "},
expectedRemapped: "",
},
{
start: "/O-A/O-B/O-C",
destination: "../O-D",
expected: []string{"/O-A/O-B => /D-A/D-B", "/O-A/O-B/O-D => /D-A/D-B/D-D"},
expectedRemapped: "/D-A/D-B/D-D",
},
{
start: "/",
destination: ".",
expected: []string{" => "},
expectedRemapped: "",
},
} {
t.Run(" "+testCase.start+" => "+testCase.destination, func(t *testing.T) {
originTree := NewMemoryTree(ctx, "O")
testTreeBuild(t, originTree, 2)
destinationTree := NewMemoryTree(ctx, "D")
//
// Mirror two trees
//
generic.TreeMirror(ctx, originTree, destinationTree, generic.NewPathFromString("/"), generic.NewMirrorOptions())
//
// collect all nodes traversed by the apply function along testCase.destination
//
collected := make([]string, 0, 10)
collect := func(ctx context.Context, origin, destination generic.NodeInterface) {
collected = append(collected, fmt.Sprintf("%s => %s", origin.GetCurrentPath().String(), destination.GetCurrentPath().String()))
}
//
// get to testCase.start and from there run the apply function to reach testCase.destination
//
nodeApply := func(ctx context.Context, origin, destination generic.NodeInterface) {
assert.True(t, generic.NodeParallelApply(ctx, origin, generic.NewPathFromString(testCase.destination), destination, generic.NewParallelApplyOptions(collect).SetWhere(generic.ApplyEachNode)))
}
assert.True(t, generic.TreeParallelApply(ctx, originTree, generic.NewPathFromString(testCase.start), destinationTree, generic.NewParallelApplyOptions(nodeApply)))
assert.EqualValues(t, testCase.expected, collected)
//
// test TreePathRemap
//
remappedPath := generic.TreePathRemap(ctx, originTree, generic.NewPathFromString(filepath.Join(testCase.start, testCase.destination)), destinationTree)
assert.EqualValues(t, testCase.expectedRemapped, remappedPath.String())
})
}
}
func TestNodeParallelApplyNoRemap(t *testing.T) {
ctx := context.Background()
for _, testCase := range []struct {
noremap bool
start string
expected []string
expectedRemapped string
}{
{
noremap: false,
start: "/O-A/O-B",
expected: []string{" => ", "/O-A => /D-A", "/O-A/O-B => /D-A/D-B"},
expectedRemapped: "/D-A/D-B",
},
{
noremap: true,
start: "/O-A/O-B",
expected: []string{" => ", "/O-A => /O-A", "/O-A/O-B => /O-A/O-B"},
expectedRemapped: "/O-A/O-B",
},
} {
t.Run(fmt.Sprintf("noremap=%v,start=%s", testCase.noremap, testCase.start), func(t *testing.T) {
originName := "O"
originTree := NewMemoryTree(ctx, originName)
testTreeBuild(t, originTree, 2)
var destinationName string
if testCase.noremap {
destinationName = originName
} else {
destinationName = "D"
}
destinationTree := NewMemoryTree(ctx, destinationName)
//
// Mirror two trees
//
generic.TreeMirror(ctx, originTree, destinationTree, generic.NewPathFromString("/"), generic.NewMirrorOptions())
//
// collect all nodes traversed by the apply function along testCase.destination
//
collected := make([]string, 0, 10)
collect := func(ctx context.Context, origin, destination generic.NodeInterface) {
collected = append(collected, fmt.Sprintf("%s => %s", origin.GetCurrentPath(), destination.GetCurrentPath()))
}
//
// get to testCase.start and from there run the apply function to reach testCase.destination
//
assert.True(t, generic.TreeParallelApply(ctx, originTree, generic.NewPathFromString(testCase.start), destinationTree, generic.NewParallelApplyOptions(collect).SetWhere(generic.ApplyEachNode).SetNoRemap(testCase.noremap)))
assert.EqualValues(t, testCase.expected, collected)
//
// test TreePathRemap
//
remappedPath := generic.TreePathRemap(ctx, originTree, generic.NewPathFromString(testCase.start), destinationTree)
assert.EqualValues(t, testCase.expectedRemapped, remappedPath.String())
})
}
}
func TestNodeParallelApplyNotFound(t *testing.T) {
ctx := context.Background()
for _, testCase := range []struct {
start string
destination string
}{
{
start: "/O-A",
destination: "O-B/???",
},
{
start: "/O-A/O-B",
destination: "???",
},
{
start: "/O-A/O-B",
destination: "../???",
},
} {
t.Run(" "+testCase.start+" => "+testCase.destination, func(t *testing.T) {
originTree := NewMemoryTree(ctx, "O")
testTreeBuild(t, originTree, 2)
destinationTree := NewMemoryTree(ctx, "D")
//
// Mirror two trees
//
generic.TreeMirror(ctx, originTree, destinationTree, generic.NewPathFromString("/"), generic.NewMirrorOptions())
//
// get to testCase.start and from there run the apply function to reach testCase.destination
//
var called bool
nodeApply := func(ctx context.Context, origin, destination generic.NodeInterface) {
called = true
found := generic.NodeParallelApply(ctx, origin, generic.NewPathFromString(testCase.destination), destination, generic.NewParallelApplyOptions(nil))
assert.False(t, found)
}
assert.True(t, generic.TreeParallelApply(ctx, originTree, generic.NewPathFromString(testCase.start), destinationTree, generic.NewParallelApplyOptions(nodeApply)))
assert.True(t, called)
})
}
}