Adding upstream version 3.10.8.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
37e9b6d587
commit
03bfe4079e
356 changed files with 28857 additions and 0 deletions
124
tree/f3/f3.go
Normal file
124
tree/f3/f3.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
objects_helper "code.forgejo.org/f3/gof3/v3/tree/f3/objects"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
)
|
||||
|
||||
type treeF3 struct {
|
||||
generic.Tree
|
||||
|
||||
options options.Interface
|
||||
objectsHelper objects_helper.Interface
|
||||
}
|
||||
|
||||
type setFunc func(parent path.Path, node generic.NodeInterface)
|
||||
|
||||
type TreeInterface interface {
|
||||
generic.TreeInterface
|
||||
|
||||
IsContainer(kind.Kind) bool
|
||||
NewFormat(kind kind.Kind) f3.Interface
|
||||
CreateChild(ctx context.Context, pathString string, set setFunc)
|
||||
GetObjectsHelper() objects_helper.Interface
|
||||
}
|
||||
|
||||
func (o *treeF3) GetChildrenKind(parentKind kind.Kind) kind.Kind {
|
||||
if childrenKind, ok := childrenKind[parentKind]; ok {
|
||||
return childrenKind
|
||||
}
|
||||
panic(fmt.Errorf("unexpected kind %s", parentKind))
|
||||
}
|
||||
|
||||
func (o *treeF3) IsContainer(kind kind.Kind) bool {
|
||||
if isContainer, ok := isContainer[kind]; ok {
|
||||
return isContainer
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *treeF3) NewFormat(kind kind.Kind) f3.Interface {
|
||||
return f3.New(string(kind))
|
||||
}
|
||||
|
||||
func (o *treeF3) GetObjectsHelper() objects_helper.Interface {
|
||||
return o.objectsHelper
|
||||
}
|
||||
|
||||
func (o *treeF3) CreateChild(ctx context.Context, pathString string, set setFunc) {
|
||||
p := generic.NewPathFromString(pathString)
|
||||
o.Apply(ctx, p, generic.NewApplyOptions(func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
|
||||
o.Trace("%s %s | %T %s", parent.String(), node.GetID(), node, node.GetKind())
|
||||
child := o.Factory(ctx, o.GetChildrenKind(node.GetKind()))
|
||||
child.SetParent(node)
|
||||
childParentPath := parent.Append(node)
|
||||
if set != nil {
|
||||
set(childParentPath, child)
|
||||
}
|
||||
child.Upsert(ctx)
|
||||
child.List(ctx)
|
||||
}))
|
||||
}
|
||||
|
||||
func newTreeF3(ctx context.Context, opts options.Interface) generic.TreeInterface {
|
||||
tree := &treeF3{}
|
||||
tree.Init(tree, opts)
|
||||
|
||||
tree.objectsHelper = objects_helper.NewObjectsHelper()
|
||||
|
||||
tree.SetDriver(GetForgeFactory(opts.GetName())(tree, opts))
|
||||
|
||||
tree.Register(kind.KindRoot, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
|
||||
return newFixedChildrenNode(ctx, tree, []kind.Kind{KindForge})
|
||||
})
|
||||
tree.Register(KindForge, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
|
||||
return newFixedChildrenNode(ctx, tree, []kind.Kind{KindTopics, KindUsers, KindOrganizations})
|
||||
})
|
||||
tree.Register(KindUser, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
|
||||
return newFixedChildrenNode(ctx, tree, []kind.Kind{KindProjects})
|
||||
})
|
||||
tree.Register(KindOrganization, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
|
||||
return newFixedChildrenNode(ctx, tree, []kind.Kind{KindProjects})
|
||||
})
|
||||
tree.Register(KindProject, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
|
||||
return newFixedChildrenNode(ctx, tree, []kind.Kind{KindRepositories, KindLabels, KindMilestones, KindIssues, KindPullRequests, KindReleases})
|
||||
})
|
||||
tree.Register(KindIssue, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
|
||||
return newFixedChildrenNode(ctx, tree, []kind.Kind{KindComments, KindReactions})
|
||||
})
|
||||
tree.Register(KindRepository, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
|
||||
return newRepositoryNode(ctx, tree)
|
||||
})
|
||||
tree.Register(KindPullRequest, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
|
||||
return newFixedChildrenNode(ctx, tree, []kind.Kind{KindComments, KindReactions, KindReviews})
|
||||
})
|
||||
tree.Register(KindReview, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
|
||||
return newFixedChildrenNode(ctx, tree, []kind.Kind{KindReviewComments, KindReactions})
|
||||
})
|
||||
tree.Register(KindComment, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
|
||||
return newFixedChildrenNode(ctx, tree, []kind.Kind{KindReactions})
|
||||
})
|
||||
tree.Register(KindRelease, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
|
||||
return newFixedChildrenNode(ctx, tree, []kind.Kind{KindAssets})
|
||||
})
|
||||
|
||||
root := tree.Factory(ctx, kind.KindRoot)
|
||||
tree.SetRoot(root)
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
func init() {
|
||||
generic.RegisterFactory("f3", newTreeF3)
|
||||
}
|
49
tree/f3/fixed_children.go
Normal file
49
tree/f3/fixed_children.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
)
|
||||
|
||||
type fixedChildrenNode struct {
|
||||
generic.Node
|
||||
|
||||
kinds []kind.Kind
|
||||
}
|
||||
|
||||
func (o *fixedChildrenNode) GetChildren() generic.ChildrenSlice {
|
||||
children := generic.NewChildrenSlice(len(o.kinds))
|
||||
for _, kind := range o.kinds {
|
||||
child := o.GetChild(id.NewNodeID(kind))
|
||||
children = append(children, child)
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
func (o *fixedChildrenNode) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
|
||||
if page > 1 {
|
||||
return generic.NewChildrenSlice(0)
|
||||
}
|
||||
return o.GetChildren()
|
||||
}
|
||||
|
||||
func newFixedChildrenNode(ctx context.Context, tree generic.TreeInterface, kinds []kind.Kind) generic.NodeInterface {
|
||||
node := &fixedChildrenNode{}
|
||||
node.Init(node)
|
||||
node.kinds = kinds
|
||||
for _, kind := range kinds {
|
||||
id := id.NewNodeID(kind)
|
||||
child := tree.Factory(ctx, kind)
|
||||
child.SetID(id)
|
||||
child.SetParent(node)
|
||||
node.SetChild(child)
|
||||
}
|
||||
return node
|
||||
}
|
30
tree/f3/forge_factory.go
Normal file
30
tree/f3/forge_factory.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
)
|
||||
|
||||
var forgeFactories = make(map[string]ForgeFactory, 10)
|
||||
|
||||
type ForgeFactory func(tree generic.TreeInterface, options any) generic.TreeDriverInterface
|
||||
|
||||
func RegisterForgeFactory(name string, factory ForgeFactory) {
|
||||
name = strings.ToLower(name)
|
||||
forgeFactories[name] = factory
|
||||
}
|
||||
|
||||
func GetForgeFactory(name string) ForgeFactory {
|
||||
name = strings.ToLower(name)
|
||||
factory, ok := forgeFactories[name]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("no forge registered for %s", name))
|
||||
}
|
||||
return factory
|
||||
}
|
181
tree/f3/helpers.go
Normal file
181
tree/f3/helpers.go
Normal file
|
@ -0,0 +1,181 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
"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/util"
|
||||
)
|
||||
|
||||
type ForgeDriverInterface interface {
|
||||
SetNative(native any)
|
||||
GetNativeID() string
|
||||
GetHelper() any
|
||||
}
|
||||
|
||||
func ConvertToAny[T any](s ...T) []any {
|
||||
a := make([]any, 0, len(s))
|
||||
for _, e := range s {
|
||||
a = append(a, e)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func ConvertNativeChild(ctx context.Context, tree generic.TreeInterface, parent generic.NodeInterface, kind kind.Kind, nativeChild any) generic.NodeInterface {
|
||||
child := tree.Factory(ctx, kind)
|
||||
child.SetParent(parent)
|
||||
childDriver := child.GetDriver().(ForgeDriverInterface)
|
||||
childDriver.SetNative(nativeChild)
|
||||
child.SetID(id.NewNodeID(childDriver.GetNativeID()))
|
||||
return child
|
||||
}
|
||||
|
||||
func ConvertListed(ctx context.Context, node generic.NodeInterface, nativeChildren ...any) generic.ChildrenSlice {
|
||||
children := generic.NewChildrenSlice(len(nativeChildren))
|
||||
|
||||
tree := node.GetTree()
|
||||
f3Tree := tree.(TreeInterface)
|
||||
kind := f3Tree.GetChildrenKind(node.GetKind())
|
||||
|
||||
for _, nativeChild := range nativeChildren {
|
||||
children = append(children, ConvertNativeChild(ctx, tree, node, kind, nativeChild))
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
func GetFirstNodeKind(node generic.NodeInterface, kind ...kind.Kind) generic.NodeInterface {
|
||||
if slices.Contains(kind, node.GetKind()) {
|
||||
return node
|
||||
}
|
||||
parent := node.GetParent()
|
||||
if parent == generic.NilNode {
|
||||
return generic.NilNode
|
||||
}
|
||||
return GetFirstNodeKind(parent, kind...)
|
||||
}
|
||||
|
||||
func GetFirstFormat[T f3.Interface](node generic.NodeInterface) T {
|
||||
f := node.NewFormat()
|
||||
switch f.(type) {
|
||||
case T:
|
||||
return node.ToFormat().(T)
|
||||
}
|
||||
parent := node.GetParent()
|
||||
if parent == generic.NilNode {
|
||||
panic(fmt.Errorf("no parent of the desired type"))
|
||||
}
|
||||
return GetFirstFormat[T](parent)
|
||||
}
|
||||
|
||||
func GetProject(node generic.NodeInterface) generic.NodeInterface {
|
||||
return GetFirstNodeKind(node, KindProject)
|
||||
}
|
||||
|
||||
func GetProjectID(node generic.NodeInterface) int64 {
|
||||
return util.ParseInt(GetProject(node).GetID().String())
|
||||
}
|
||||
|
||||
func GetProjectName(node generic.NodeInterface) string {
|
||||
return GetProject(node).ToFormat().(*f3.Project).Name
|
||||
}
|
||||
|
||||
func GetOwner(node generic.NodeInterface) generic.NodeInterface {
|
||||
return GetFirstNodeKind(node, KindUser, KindOrganization)
|
||||
}
|
||||
|
||||
func GetOwnerID(node generic.NodeInterface) int64 {
|
||||
return GetOwner(node).GetID().Int64()
|
||||
}
|
||||
|
||||
func GetReactionable(node generic.NodeInterface) generic.NodeInterface {
|
||||
return GetFirstNodeKind(node, KindComment, KindIssue, KindPullRequest)
|
||||
}
|
||||
|
||||
func GetReactionableID(node generic.NodeInterface) int64 {
|
||||
return GetReactionable(node).GetID().Int64()
|
||||
}
|
||||
|
||||
func GetCommentable(node generic.NodeInterface) generic.NodeInterface {
|
||||
return GetFirstNodeKind(node, KindIssue, KindPullRequest)
|
||||
}
|
||||
|
||||
func GetCommentableID(node generic.NodeInterface) int64 {
|
||||
return GetCommentable(node).GetID().Int64()
|
||||
}
|
||||
|
||||
func GetComment(node generic.NodeInterface) generic.NodeInterface {
|
||||
return GetFirstNodeKind(node, KindComment, KindReviewComment)
|
||||
}
|
||||
|
||||
func GetCommentID(node generic.NodeInterface) int64 {
|
||||
return GetComment(node).GetID().Int64()
|
||||
}
|
||||
|
||||
func GetRelease(node generic.NodeInterface) generic.NodeInterface {
|
||||
return GetFirstNodeKind(node, KindRelease)
|
||||
}
|
||||
|
||||
func GetReleaseID(node generic.NodeInterface) int64 {
|
||||
return GetRelease(node).GetID().Int64()
|
||||
}
|
||||
|
||||
func GetPullRequest(node generic.NodeInterface) generic.NodeInterface {
|
||||
return GetFirstNodeKind(node, KindPullRequest)
|
||||
}
|
||||
|
||||
func GetPullRequestID(node generic.NodeInterface) int64 {
|
||||
return GetPullRequest(node).GetID().Int64()
|
||||
}
|
||||
|
||||
func GetReview(node generic.NodeInterface) generic.NodeInterface {
|
||||
return GetFirstNodeKind(node, KindReview)
|
||||
}
|
||||
|
||||
func GetReviewID(node generic.NodeInterface) int64 {
|
||||
return GetReview(node).GetID().Int64()
|
||||
}
|
||||
|
||||
func GetReviewComment(node generic.NodeInterface) generic.NodeInterface {
|
||||
return GetFirstNodeKind(node, KindReviewComment)
|
||||
}
|
||||
|
||||
func GetReviewCommentID(node generic.NodeInterface) int64 {
|
||||
return GetReviewComment(node).GetID().Int64()
|
||||
}
|
||||
|
||||
func GetOwnerName(node generic.NodeInterface) string {
|
||||
owner := GetOwner(node)
|
||||
if owner == generic.NilNode {
|
||||
panic(fmt.Errorf("no user or organization parent for %s", node))
|
||||
}
|
||||
switch f := owner.ToFormat().(type) {
|
||||
case *f3.User:
|
||||
return f.UserName
|
||||
case *f3.Organization:
|
||||
return f.Name
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected type %T", owner.ToFormat()))
|
||||
}
|
||||
}
|
||||
|
||||
func GetUsernameFromID(ctx context.Context, tree TreeInterface, id int64) string {
|
||||
var name string
|
||||
getName := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
|
||||
name = node.ToFormat().(*f3.User).UserName
|
||||
}
|
||||
p := NewUserPath(id)
|
||||
if !tree.ApplyAndGet(ctx, p, generic.NewApplyOptions(getName)) {
|
||||
panic(fmt.Errorf("%s not found", p))
|
||||
}
|
||||
return name
|
||||
}
|
102
tree/f3/kind.go
Normal file
102
tree/f3/kind.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
// 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/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
)
|
||||
|
||||
const (
|
||||
KindAsset = f3.ResourceAsset
|
||||
KindAssets = f3.ResourceAssets
|
||||
KindComment = f3.ResourceComment
|
||||
KindComments = f3.ResourceComments
|
||||
KindForge = f3.ResourceForge
|
||||
KindIssue = f3.ResourceIssue
|
||||
KindIssues = f3.ResourceIssues
|
||||
KindLabel = f3.ResourceLabel
|
||||
KindLabels = f3.ResourceLabels
|
||||
KindMilestone = f3.ResourceMilestone
|
||||
KindMilestones = f3.ResourceMilestones
|
||||
KindOrganization = f3.ResourceOrganization
|
||||
KindOrganizations = f3.ResourceOrganizations
|
||||
KindProject = f3.ResourceProject
|
||||
KindProjects = f3.ResourceProjects
|
||||
KindPullRequest = f3.ResourcePullRequest
|
||||
KindPullRequests = f3.ResourcePullRequests
|
||||
KindReaction = f3.ResourceReaction
|
||||
KindReactions = f3.ResourceReactions
|
||||
KindRelease = f3.ResourceRelease
|
||||
KindReleases = f3.ResourceReleases
|
||||
KindRepository = f3.ResourceRepository
|
||||
KindRepositories = f3.ResourceRepositories
|
||||
KindReview = f3.ResourceReview
|
||||
KindReviews = f3.ResourceReviews
|
||||
KindReviewComment = f3.ResourceReviewComment
|
||||
KindReviewComments = f3.ResourceReviewComments
|
||||
KindTopic = f3.ResourceTopic
|
||||
KindTopics = f3.ResourceTopics
|
||||
KindUser = f3.ResourceUser
|
||||
KindUsers = f3.ResourceUsers
|
||||
)
|
||||
|
||||
var isContainer = map[kind.Kind]bool{
|
||||
kind.KindRoot: true,
|
||||
KindAssets: true,
|
||||
KindComments: true,
|
||||
KindIssues: true,
|
||||
KindLabels: true,
|
||||
KindMilestones: true,
|
||||
KindOrganizations: true,
|
||||
KindProjects: true,
|
||||
KindPullRequests: true,
|
||||
KindReactions: true,
|
||||
KindReleases: true,
|
||||
KindRepositories: true,
|
||||
KindReviews: true,
|
||||
KindReviewComments: true,
|
||||
KindTopics: true,
|
||||
KindUsers: true,
|
||||
}
|
||||
|
||||
var childrenKind = map[kind.Kind]kind.Kind{
|
||||
kind.KindRoot: KindForge,
|
||||
KindAssets: KindAsset,
|
||||
KindComments: KindComment,
|
||||
KindIssues: KindIssue,
|
||||
KindLabels: KindLabel,
|
||||
KindMilestones: KindMilestone,
|
||||
KindOrganizations: KindOrganization,
|
||||
KindProjects: KindProject,
|
||||
KindPullRequests: KindPullRequest,
|
||||
KindReactions: KindReaction,
|
||||
KindReleases: KindRelease,
|
||||
KindRepositories: KindRepository,
|
||||
KindReviews: KindReview,
|
||||
KindReviewComments: KindReviewComment,
|
||||
KindTopics: KindTopic,
|
||||
KindUsers: KindUser,
|
||||
}
|
||||
|
||||
var containerChildFixedID = map[kind.Kind]bool{
|
||||
kind.KindRoot: true,
|
||||
KindAssets: false,
|
||||
KindComments: false,
|
||||
KindForge: true,
|
||||
KindIssues: false,
|
||||
KindLabels: false,
|
||||
KindMilestones: false,
|
||||
KindOrganizations: false,
|
||||
KindProjects: false,
|
||||
KindPullRequests: false,
|
||||
KindReactions: false,
|
||||
KindReleases: false,
|
||||
KindRepositories: false,
|
||||
KindReviewComments: false,
|
||||
KindReviews: false,
|
||||
KindTopics: false,
|
||||
KindUsers: false,
|
||||
}
|
27
tree/f3/label.go
Normal file
27
tree/f3/label.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
)
|
||||
|
||||
func NewLabelPathString[T any](projectPath string, id T) string {
|
||||
return fmt.Sprintf("%s/labels/%v", projectPath, id)
|
||||
}
|
||||
|
||||
func NewLabelReference[T any](projectPath string, id T) *f3.Reference {
|
||||
return f3.NewReference(NewLabelPathString(projectPath, id))
|
||||
}
|
||||
|
||||
func NewIssueLabelReference[T any](id T) *f3.Reference {
|
||||
return f3.NewReference(NewLabelPathString("../..", id))
|
||||
}
|
||||
|
||||
func NewPullRequestLabelReference[T any](id T) *f3.Reference {
|
||||
return f3.NewReference(NewLabelPathString("../..", id))
|
||||
}
|
23
tree/f3/milestone.go
Normal file
23
tree/f3/milestone.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
)
|
||||
|
||||
func NewMilestonePathString[T any](projectPath string, id T) string {
|
||||
return fmt.Sprintf("%s/milestones/%v", projectPath, id)
|
||||
}
|
||||
|
||||
func NewMilestoneReference[T any](projectPath string, id T) *f3.Reference {
|
||||
return f3.NewReference(NewMilestonePathString(projectPath, id))
|
||||
}
|
||||
|
||||
func NewIssueMilestoneReference[T any](id T) *f3.Reference {
|
||||
return f3.NewReference(NewMilestonePathString("../..", id))
|
||||
}
|
99
tree/f3/objects/objects.go
Normal file
99
tree/f3/objects/objects.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package objects
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Save(rc io.ReadCloser) (string, string)
|
||||
}
|
||||
|
||||
type helper struct {
|
||||
dir *string
|
||||
}
|
||||
|
||||
func (o *helper) getDir() string {
|
||||
if o.dir == nil {
|
||||
dir, err := os.MkdirTemp("", "objectHelper")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
runtime.SetFinalizer(o, func(o *helper) {
|
||||
err := os.RemoveAll(dir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
o.dir = &dir
|
||||
}
|
||||
return *o.dir
|
||||
}
|
||||
|
||||
func (o *helper) getPath(sha string) string {
|
||||
return filepath.Join(o.getDir(), sha[0:2], sha[2:4], sha)
|
||||
}
|
||||
|
||||
func (o *helper) Save(rc io.ReadCloser) (string, string) {
|
||||
tempFile, err := os.CreateTemp("", "object")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tempRemoved := false
|
||||
defer func() {
|
||||
if !tempRemoved {
|
||||
_ = os.Remove(tempFile.Name())
|
||||
}
|
||||
}()
|
||||
|
||||
// reader
|
||||
defer rc.Close()
|
||||
|
||||
// writer to file
|
||||
f, err := os.OpenFile(tempFile.Name(), os.O_CREATE|os.O_RDWR, 0o644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// writer to sha256
|
||||
h := sha256.New()
|
||||
|
||||
// copy reader to file & sha256
|
||||
w := io.MultiWriter(f, h)
|
||||
if _, err := io.Copy(w, rc); err != nil {
|
||||
panic(fmt.Errorf("while copying object: %w", err))
|
||||
}
|
||||
|
||||
// finalize writer to sha256
|
||||
sha := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
// finalize writer to file
|
||||
if err := tempFile.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
path := o.getPath(sha)
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.Rename(tempFile.Name(), path); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tempRemoved = true
|
||||
|
||||
return sha, path
|
||||
}
|
||||
|
||||
func NewObjectsHelper() Interface {
|
||||
return &helper{}
|
||||
}
|
68
tree/f3/objects/sha.go
Normal file
68
tree/f3/objects/sha.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package objects
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
options_http "code.forgejo.org/f3/gof3/v3/options/http"
|
||||
)
|
||||
|
||||
type SHASetter interface {
|
||||
SetSHA(sha string)
|
||||
}
|
||||
|
||||
func FuncReadURLAndSetSHA(newHTTPClient options_http.NewMigrationHTTPClientFun, url string, sha SHASetter) func() io.ReadCloser {
|
||||
return func() io.ReadCloser {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
httpClient := newHTTPClient()
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("while downloading %s %w", url, err))
|
||||
}
|
||||
|
||||
return FuncReadAndSetSHA(resp.Body, sha)()
|
||||
}
|
||||
}
|
||||
|
||||
type readAndSetSHA struct {
|
||||
reader io.Reader
|
||||
closer io.Closer
|
||||
sha SHASetter
|
||||
hasher hash.Hash
|
||||
}
|
||||
|
||||
func (o *readAndSetSHA) Read(b []byte) (n int, err error) {
|
||||
return o.reader.Read(b)
|
||||
}
|
||||
|
||||
func (o *readAndSetSHA) Close() error {
|
||||
o.sha.SetSHA(hex.EncodeToString(o.hasher.Sum(nil)))
|
||||
return o.closer.Close()
|
||||
}
|
||||
|
||||
func newReadAndSetSHA(reader io.ReadCloser, sha SHASetter) *readAndSetSHA {
|
||||
hasher := sha256.New()
|
||||
return &readAndSetSHA{
|
||||
reader: io.TeeReader(reader, hasher),
|
||||
closer: reader,
|
||||
sha: sha,
|
||||
hasher: hasher,
|
||||
}
|
||||
}
|
||||
|
||||
func FuncReadAndSetSHA(reader io.ReadCloser, sha SHASetter) func() io.ReadCloser {
|
||||
return func() io.ReadCloser {
|
||||
return newReadAndSetSHA(reader, sha)
|
||||
}
|
||||
}
|
35
tree/f3/objects/sha_test.go
Normal file
35
tree/f3/objects/sha_test.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package objects
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type sha string
|
||||
|
||||
func (o *sha) SetSHA(s string) {
|
||||
*o = sha(s)
|
||||
}
|
||||
|
||||
func (o *sha) GetSHA() string {
|
||||
return string(*o)
|
||||
}
|
||||
|
||||
func Test_FuncReadAndSetSHA(t *testing.T) {
|
||||
content := "CONTENT"
|
||||
r := strings.NewReader(content)
|
||||
s := new(sha)
|
||||
f := FuncReadAndSetSHA(io.NopCloser(r), s)()
|
||||
c, err := io.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.Close())
|
||||
require.Equal(t, "65f23e22a9bfedda96929b3cfcb8b6d2fdd34a2e877ddb81f45d79ab05710e12", s.GetSHA())
|
||||
require.Equal(t, content, string(c))
|
||||
}
|
11
tree/f3/organizations.go
Normal file
11
tree/f3/organizations.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
// 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/tree/generic"
|
||||
)
|
||||
|
||||
var OrganizationsPath = generic.NewPathFromString("/forge/organizations")
|
188
tree/f3/path.go
Normal file
188
tree/f3/path.go
Normal file
|
@ -0,0 +1,188 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
generic_path "code.forgejo.org/f3/gof3/v3/path"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
)
|
||||
|
||||
type Path interface {
|
||||
NodeIDs() []id.NodeID
|
||||
OwnerAndProjectID() (owner, project int64)
|
||||
|
||||
AppendID(id string) Path
|
||||
Ignore() Path
|
||||
|
||||
generic_path.Path
|
||||
Root() Path
|
||||
|
||||
Forge() Path
|
||||
SetForge() Path
|
||||
|
||||
Assets() Path
|
||||
SetAssets() Path
|
||||
|
||||
Comments() Path
|
||||
SetComments() Path
|
||||
|
||||
Issues() Path
|
||||
SetIssues() Path
|
||||
|
||||
Labels() Path
|
||||
SetLabels() Path
|
||||
|
||||
Milestones() Path
|
||||
SetMilestones() Path
|
||||
|
||||
Owners() Path
|
||||
SetOwners(owners kind.Kind) Path
|
||||
|
||||
Organizations() Path
|
||||
SetOrganizations() Path
|
||||
|
||||
Projects() Path
|
||||
SetProjects() Path
|
||||
|
||||
PullRequests() Path
|
||||
SetPullRequests() Path
|
||||
|
||||
Reactions() Path
|
||||
SetReactions() Path
|
||||
|
||||
Releases() Path
|
||||
SetReleases() Path
|
||||
|
||||
Repositories() Path
|
||||
SetRepositories() Path
|
||||
|
||||
Reviews() Path
|
||||
SetReviews() Path
|
||||
|
||||
ReviewComments() Path
|
||||
SetReviewComments() Path
|
||||
|
||||
Topics() Path
|
||||
SetTopics() Path
|
||||
|
||||
Users() Path
|
||||
SetUsers() Path
|
||||
}
|
||||
|
||||
type f3path struct {
|
||||
generic_path.Implementation
|
||||
}
|
||||
|
||||
func (o f3path) popKind(k ...kind.Kind) Path {
|
||||
firstKind := kind.Kind(o.First().(generic.NodeInterface).GetID().String())
|
||||
if !slices.Contains(k, firstKind) {
|
||||
panic(fmt.Errorf("%s expected one of %s got %s", o, k, firstKind))
|
||||
}
|
||||
return ToPath(o.RemoveFirst())
|
||||
}
|
||||
|
||||
func (o f3path) NodeIDs() []id.NodeID {
|
||||
nodeIDs := make([]id.NodeID, 0, o.Length())
|
||||
collectID := false
|
||||
for _, node := range o.Root().All() {
|
||||
if collectID {
|
||||
nodeIDs = append(nodeIDs, node.(generic.NodeInterface).GetID())
|
||||
collectID = false
|
||||
continue
|
||||
}
|
||||
kind := kind.Kind(node.(generic.NodeInterface).GetID().String())
|
||||
fixedID, ok := containerChildFixedID[kind]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("%s unexpected kind %s", o.All(), kind))
|
||||
}
|
||||
if !fixedID {
|
||||
collectID = true
|
||||
}
|
||||
}
|
||||
return nodeIDs
|
||||
}
|
||||
|
||||
func (o f3path) OwnerAndProjectID() (owner, project int64) {
|
||||
nodeIDs := o.NodeIDs()
|
||||
return nodeIDs[0].Int64(), nodeIDs[1].Int64()
|
||||
}
|
||||
|
||||
func (o f3path) Root() Path { return o.popKind(kind.Kind("")) }
|
||||
|
||||
func (o f3path) Forge() Path { return o.popKind(KindForge) }
|
||||
func (o f3path) SetForge() Path { return o.appendKind(KindForge) }
|
||||
|
||||
func (o f3path) Assets() Path { return o.popKind(KindAssets) }
|
||||
func (o f3path) SetAssets() Path { return o.appendKind(KindAssets) }
|
||||
|
||||
func (o f3path) Comments() Path { return o.popKind(KindComments) }
|
||||
func (o f3path) SetComments() Path { return o.appendKind(KindComments) }
|
||||
|
||||
func (o f3path) Issues() Path { return o.popKind(KindIssues) }
|
||||
func (o f3path) SetIssues() Path { return o.appendKind(KindIssues) }
|
||||
|
||||
func (o f3path) Labels() Path { return o.popKind(KindLabels) }
|
||||
func (o f3path) SetLabels() Path { return o.appendKind(KindLabels) }
|
||||
|
||||
func (o f3path) Milestones() Path { return o.popKind(KindMilestones) }
|
||||
func (o f3path) SetMilestones() Path { return o.appendKind(KindMilestones) }
|
||||
|
||||
func (o f3path) Organizations() Path { return o.popKind(KindOrganizations) }
|
||||
func (o f3path) SetOrganizations() Path { return o.appendKind(KindOrganizations) }
|
||||
|
||||
func (o f3path) Projects() Path { return o.popKind(KindProjects) }
|
||||
func (o f3path) SetProjects() Path { return o.appendKind(KindProjects) }
|
||||
|
||||
func (o f3path) PullRequests() Path { return o.popKind(KindPullRequests) }
|
||||
func (o f3path) SetPullRequests() Path { return o.appendKind(KindPullRequests) }
|
||||
|
||||
func (o f3path) Reactions() Path { return o.popKind(KindReactions) }
|
||||
func (o f3path) SetReactions() Path { return o.appendKind(KindReactions) }
|
||||
|
||||
func (o f3path) Releases() Path { return o.popKind(KindReleases) }
|
||||
func (o f3path) SetReleases() Path { return o.appendKind(KindReleases) }
|
||||
|
||||
func (o f3path) Repositories() Path { return o.popKind(KindRepositories) }
|
||||
func (o f3path) SetRepositories() Path { return o.appendKind(KindRepositories) }
|
||||
|
||||
func (o f3path) Reviews() Path { return o.popKind(KindReviews) }
|
||||
func (o f3path) SetReviews() Path { return o.appendKind(KindReviews) }
|
||||
|
||||
func (o f3path) ReviewComments() Path { return o.popKind(KindReviewComments) }
|
||||
func (o f3path) SetReviewComments() Path { return o.appendKind(KindReviewComments) }
|
||||
|
||||
func (o f3path) Topics() Path { return o.popKind(KindTopics) }
|
||||
func (o f3path) SetTopics() Path { return o.appendKind(KindTopics) }
|
||||
|
||||
func (o f3path) Users() Path { return o.popKind(KindUsers) }
|
||||
func (o f3path) SetUsers() Path { return o.appendKind(KindUsers) }
|
||||
|
||||
func (o f3path) Owners() Path { return o.popKind(KindUsers, KindOrganizations) }
|
||||
func (o f3path) SetOwners(owners kind.Kind) Path { return o.appendKind(owners) }
|
||||
|
||||
func (o f3path) AppendID(id string) Path {
|
||||
return ToPath(o.Append(generic.NewNodeFromID(id)))
|
||||
}
|
||||
|
||||
func (o f3path) appendKind(kind kind.Kind) Path {
|
||||
return o.AppendID(string(kind))
|
||||
}
|
||||
|
||||
func (o f3path) Ignore() Path {
|
||||
return ToPath(o.RemoveFirst())
|
||||
}
|
||||
|
||||
func ToPath(other generic_path.Path) Path {
|
||||
return f3path{other.(generic_path.Implementation)}
|
||||
}
|
||||
|
||||
func NewPathFromString(pathString string) Path {
|
||||
return ToPath(generic.NewPathFromString(pathString))
|
||||
}
|
181
tree/f3/path_test.go
Normal file
181
tree/f3/path_test.go
Normal file
|
@ -0,0 +1,181 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
"code.forgejo.org/f3/gof3/v3/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestF3Path(t *testing.T) {
|
||||
p := NewPathFromString("/")
|
||||
p = p.SetForge()
|
||||
{
|
||||
p.Root().Forge()
|
||||
}
|
||||
nodeIDs := make([]id.NodeID, 0, 10)
|
||||
{
|
||||
p := p.SetOwners(KindUsers)
|
||||
i := "1"
|
||||
ownerID := util.ParseInt(i)
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Users().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
|
||||
{
|
||||
p := p.SetProjects()
|
||||
i := "2"
|
||||
projectID := util.ParseInt(i)
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Users().Ignore().Projects().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
owner, project := p.OwnerAndProjectID()
|
||||
assert.EqualValues(t, ownerID, owner)
|
||||
assert.EqualValues(t, projectID, project)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetAssets()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Assets().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetComments()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Comments().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetIssues()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Issues().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetLabels()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Labels().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetMilestones()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Milestones().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetOrganizations()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Organizations().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetProjects()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Projects().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetPullRequests()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().PullRequests().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetReactions()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Reactions().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetReleases()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Releases().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetRepositories()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Repositories().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetReviews()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Reviews().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetReviewComments()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().ReviewComments().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetTopics()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Topics().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
|
||||
{
|
||||
p := p.SetUsers()
|
||||
i := "something"
|
||||
nodeIDs := append(nodeIDs, id.NewNodeID(i))
|
||||
p = p.AppendID(i)
|
||||
assert.EqualValues(t, id.NewNodeID(i), p.Root().Forge().Users().First().(generic.NodeInterface).GetID())
|
||||
assert.EqualValues(t, nodeIDs, p.NodeIDs())
|
||||
}
|
||||
}
|
29
tree/f3/project.go
Normal file
29
tree/f3/project.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
)
|
||||
|
||||
func NewProjectPathString[U, P any](owners string, user U, project P) string {
|
||||
return fmt.Sprintf("/forge/%s/%v/projects/%v", owners, user, project)
|
||||
}
|
||||
|
||||
func NewProjectReference[U, P any](owners string, user U, project P) *f3.Reference {
|
||||
return f3.NewReference(NewProjectPathString(owners, user, project))
|
||||
}
|
||||
|
||||
func ResolveProjectReference(ctx context.Context, tree generic.TreeInterface, r *f3.Reference) (string, string) {
|
||||
project := tree.Find(generic.NewPathFromString(r.Get()))
|
||||
if project == generic.NilNode {
|
||||
panic(fmt.Errorf("%s not found", r.Get()))
|
||||
}
|
||||
return GetOwnerName(project), GetProjectName(project)
|
||||
}
|
47
tree/f3/pullrequest.go
Normal file
47
tree/f3/pullrequest.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
)
|
||||
|
||||
type PullRequestDriverInterface interface {
|
||||
GetPullRequestHead() string
|
||||
GetPullRequestRef() string
|
||||
GetPullRequestPushRefs() []string
|
||||
}
|
||||
|
||||
type PullRequestNodeDriverProxyInterface interface {
|
||||
PullRequestDriverInterface
|
||||
}
|
||||
|
||||
type PullRequestNodeInterface interface {
|
||||
generic.NodeInterface
|
||||
PullRequestNodeDriverProxyInterface
|
||||
}
|
||||
|
||||
type pullRequestNode struct {
|
||||
generic.Node
|
||||
}
|
||||
|
||||
func (o *pullRequestNode) GetPullRequestHead() string {
|
||||
return o.GetDriver().(PullRequestDriverInterface).GetPullRequestHead()
|
||||
}
|
||||
|
||||
func (o *pullRequestNode) GetPullRequestRef() string {
|
||||
return o.GetDriver().(PullRequestDriverInterface).GetPullRequestRef()
|
||||
}
|
||||
|
||||
func (o *pullRequestNode) GetPullRequestPushRefs() []string {
|
||||
return o.GetDriver().(PullRequestDriverInterface).GetPullRequestPushRefs()
|
||||
}
|
||||
|
||||
func newPullRequestNode(ctx context.Context, tree generic.TreeInterface) generic.NodeInterface {
|
||||
node := &pullRequestNode{}
|
||||
return node.Init(node)
|
||||
}
|
66
tree/f3/repository.go
Normal file
66
tree/f3/repository.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
)
|
||||
|
||||
type RepositoryDriverInterface interface {
|
||||
GetRepositoryURL() string
|
||||
GetRepositoryPushURL() string
|
||||
GetRepositoryInternalRefs() []string
|
||||
}
|
||||
|
||||
type RepositoryNodeDriverProxyInterface interface {
|
||||
RepositoryDriverInterface
|
||||
}
|
||||
|
||||
type RepositoryNodeInterface interface {
|
||||
generic.NodeInterface
|
||||
RepositoryNodeDriverProxyInterface
|
||||
}
|
||||
|
||||
type repositoryNode struct {
|
||||
generic.Node
|
||||
}
|
||||
|
||||
func (o *repositoryNode) GetRepositoryURL() string {
|
||||
return o.GetDriver().(RepositoryDriverInterface).GetRepositoryURL()
|
||||
}
|
||||
|
||||
func (o *repositoryNode) GetRepositoryPushURL() string {
|
||||
return o.GetDriver().(RepositoryDriverInterface).GetRepositoryPushURL()
|
||||
}
|
||||
|
||||
func (o *repositoryNode) GetRepositoryInternalRefs() []string {
|
||||
return o.GetDriver().(RepositoryDriverInterface).GetRepositoryInternalRefs()
|
||||
}
|
||||
|
||||
func newRepositoryNode(ctx context.Context, tree generic.TreeInterface) generic.NodeInterface {
|
||||
node := &repositoryNode{}
|
||||
return node.Init(node)
|
||||
}
|
||||
|
||||
func NewRepositoryPath[T, U any](owners string, owner T, project U) path.Path {
|
||||
return generic.NewPathFromString(NewRepositoryPathString(owners, owner, project))
|
||||
}
|
||||
|
||||
func NewRepositoryPathString[T, U any](owners string, owner T, project U) string {
|
||||
return fmt.Sprintf("%s/%v/projects/%v/repositories/vcs", owners, owner, project)
|
||||
}
|
||||
|
||||
func NewRepositoryReference[T, U any](owners string, owner T, project U) *f3.Reference {
|
||||
return f3.NewReference(NewRepositoryPathString(owners, owner, project))
|
||||
}
|
||||
|
||||
func NewPullRequestSameRepositoryReference() *f3.Reference {
|
||||
return f3.NewReference("../../repositories/vcs")
|
||||
}
|
23
tree/f3/topic.go
Normal file
23
tree/f3/topic.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
)
|
||||
|
||||
func NewTopicPath[T any](id T) Path {
|
||||
return NewPathFromString(NewTopicPathString(id))
|
||||
}
|
||||
|
||||
func NewTopicPathString[T any](id T) string {
|
||||
return fmt.Sprintf("/forge/topics/%v", id)
|
||||
}
|
||||
|
||||
func NewTopicReference[T any](id T) *f3.Reference {
|
||||
return f3.NewReference(NewTopicPathString(id))
|
||||
}
|
11
tree/f3/topics.go
Normal file
11
tree/f3/topics.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
// 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/tree/generic"
|
||||
)
|
||||
|
||||
var TopicsPath = generic.NewPathFromString("/forge/topics")
|
23
tree/f3/user.go
Normal file
23
tree/f3/user.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
)
|
||||
|
||||
func NewUserPath[T any](id T) Path {
|
||||
return NewPathFromString(NewUserPathString(id))
|
||||
}
|
||||
|
||||
func NewUserPathString[T any](id T) string {
|
||||
return fmt.Sprintf("/forge/users/%v", id)
|
||||
}
|
||||
|
||||
func NewUserReference[T any](id T) *f3.Reference {
|
||||
return f3.NewReference(NewUserPathString(id))
|
||||
}
|
11
tree/f3/users.go
Normal file
11
tree/f3/users.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
// 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/tree/generic"
|
||||
)
|
||||
|
||||
var UsersPath = generic.NewPathFromString("/forge/users")
|
72
tree/generic/compare.go
Normal file
72
tree/generic/compare.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
)
|
||||
|
||||
func NodeCompare(ctx context.Context, a, b NodeInterface) bool {
|
||||
a.Trace("a '%s' | b '%s'", a.GetCurrentPath().ReadableString(), b.GetCurrentPath().ReadableString())
|
||||
|
||||
if a.GetKind() != b.GetKind() {
|
||||
a.Trace("kind is different a = %s | b = %s", a.GetKind(), b.GetKind())
|
||||
return false
|
||||
}
|
||||
|
||||
if diff := a.GetTree().Diff(a, b); diff != "" {
|
||||
a.Trace("difference %s", diff)
|
||||
return false
|
||||
}
|
||||
|
||||
aChildren := a.GetNodeChildren()
|
||||
bChildren := b.GetNodeChildren()
|
||||
aLen := len(aChildren)
|
||||
bLen := len(bChildren)
|
||||
if aLen != bLen {
|
||||
a.Trace("children count is different a = %d | b = %d", aLen, bLen)
|
||||
return false
|
||||
}
|
||||
|
||||
for aID, aChild := range aChildren {
|
||||
bID := aChild.GetID()
|
||||
mappedID := aChild.GetMappedID()
|
||||
if mappedID != id.NilID {
|
||||
bID = mappedID
|
||||
}
|
||||
bChild, ok := bChildren[bID]
|
||||
if !ok {
|
||||
a.Trace("there is no child in 'b' with id %s matching the child in 'a' with id %s", bID, aID)
|
||||
return false
|
||||
}
|
||||
|
||||
if !NodeCompare(ctx, aChild, bChild) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func TreeCompare(ctx context.Context, aTree TreeInterface, aPath path.Path, bTree TreeInterface, bPath path.Path) bool {
|
||||
aTree.Trace("'%s' => '%s'", aPath.ReadableString(), bPath.ReadableString())
|
||||
|
||||
a := aTree.Find(aPath)
|
||||
if a == NilNode {
|
||||
aTree.Trace("a does not have %s", aPath.ReadableString())
|
||||
return false
|
||||
}
|
||||
|
||||
b := bTree.Find(bPath)
|
||||
if b == NilNode {
|
||||
aTree.Trace("b does not have %s", bPath.ReadableString())
|
||||
return false
|
||||
}
|
||||
|
||||
return NodeCompare(ctx, a, b)
|
||||
}
|
226
tree/generic/compare_test.go
Normal file
226
tree/generic/compare_test.go
Normal file
|
@ -0,0 +1,226 @@
|
|||
// 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/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type compareFormat struct {
|
||||
f3.Common
|
||||
V int
|
||||
}
|
||||
|
||||
func (o *compareFormat) Clone() f3.Interface {
|
||||
clone := &compareFormat{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
||||
|
||||
type compareNodeDriver struct {
|
||||
NullDriver
|
||||
v int
|
||||
ID string
|
||||
}
|
||||
|
||||
func (o *compareNodeDriver) NewFormat() f3.Interface {
|
||||
return &compareFormat{
|
||||
Common: f3.NewCommon(o.ID),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *compareNodeDriver) ToFormat() f3.Interface {
|
||||
f := o.NewFormat().(*compareFormat)
|
||||
f.V = o.v
|
||||
return f
|
||||
}
|
||||
|
||||
func (o *compareNodeDriver) FromFormat(f f3.Interface) {
|
||||
fc := f.(*compareFormat)
|
||||
o.v = fc.V
|
||||
o.ID = fc.GetID()
|
||||
}
|
||||
|
||||
func newCompareNodeDriver() NodeDriverInterface {
|
||||
return &compareNodeDriver{}
|
||||
}
|
||||
|
||||
type compareTreeDriver struct {
|
||||
NullTreeDriver
|
||||
}
|
||||
|
||||
func newCompareTreeDriver() TreeDriverInterface {
|
||||
return &compareTreeDriver{}
|
||||
}
|
||||
|
||||
func (o *compareTreeDriver) Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface {
|
||||
d := newCompareNodeDriver()
|
||||
d.SetTreeDriver(o)
|
||||
return d
|
||||
}
|
||||
|
||||
func newCompareTree() TreeInterface {
|
||||
tree := &testTree{}
|
||||
tree.Init(tree, newTestOptions())
|
||||
tree.Trace("init done")
|
||||
tree.SetDriver(newCompareTreeDriver())
|
||||
tree.Register(kindCompareNode, func(ctx context.Context, kind kind.Kind) NodeInterface {
|
||||
node := &compareNode{}
|
||||
return node.Init(node)
|
||||
})
|
||||
return tree
|
||||
}
|
||||
|
||||
var kindCompareNode = kind.Kind("compare")
|
||||
|
||||
type compareNode struct {
|
||||
Node
|
||||
}
|
||||
|
||||
func TestTreeCompare(t *testing.T) {
|
||||
tree := newTestTree()
|
||||
log := logger.NewCaptureLogger()
|
||||
log.SetLevel(logger.Trace)
|
||||
tree.SetLogger(log)
|
||||
|
||||
root := tree.Factory(context.Background(), kindTestNodeLevelOne)
|
||||
tree.SetRoot(root)
|
||||
assert.True(t, TreeCompare(context.Background(), tree, path.NewPathFromString(NewElementNode, ""), tree, path.NewPathFromString(NewElementNode, "")))
|
||||
|
||||
log.Reset()
|
||||
assert.False(t, TreeCompare(context.Background(), tree, path.NewPathFromString(NewElementNode, "/notfound"), tree, path.NewPathFromString(NewElementNode, "")))
|
||||
assert.Contains(t, log.String(), "a does not have /notfound")
|
||||
|
||||
log.Reset()
|
||||
assert.False(t, TreeCompare(context.Background(), tree, path.NewPathFromString(NewElementNode, "/"), tree, path.NewPathFromString(NewElementNode, "/notfound")))
|
||||
assert.Contains(t, log.String(), "b does not have /notfound")
|
||||
}
|
||||
|
||||
func TestNodeCompare(t *testing.T) {
|
||||
log := logger.NewCaptureLogger()
|
||||
log.SetLevel(logger.Trace)
|
||||
|
||||
treeA := newCompareTree()
|
||||
treeA.SetLogger(log)
|
||||
|
||||
treeB := newCompareTree()
|
||||
treeB.SetLogger(log)
|
||||
|
||||
nodeA := treeA.Factory(context.Background(), kindCompareNode)
|
||||
nodeA.SetID(id.NewNodeID("root"))
|
||||
assert.True(t, NodeCompare(context.Background(), nodeA, nodeA))
|
||||
|
||||
t.Run("different kind", func(t *testing.T) {
|
||||
log.Reset()
|
||||
other := treeB.Factory(context.Background(), kindCompareNode)
|
||||
other.SetKind("other")
|
||||
assert.False(t, NodeCompare(context.Background(), nodeA, other))
|
||||
assert.Contains(t, log.String(), "kind is different")
|
||||
})
|
||||
|
||||
t.Run("difference", func(t *testing.T) {
|
||||
log.Reset()
|
||||
other := treeB.Factory(context.Background(), kindCompareNode)
|
||||
other.FromFormat(&compareFormat{V: 123456})
|
||||
|
||||
assert.False(t, NodeCompare(context.Background(), nodeA, other))
|
||||
assert.Contains(t, log.String(), "difference")
|
||||
assert.Contains(t, log.String(), "123456")
|
||||
})
|
||||
|
||||
t.Run("children count", func(t *testing.T) {
|
||||
log.Reset()
|
||||
other := treeB.Factory(context.Background(), kindCompareNode)
|
||||
other.SetChild(treeB.Factory(context.Background(), kindCompareNode))
|
||||
|
||||
assert.False(t, NodeCompare(context.Background(), nodeA, other))
|
||||
assert.Contains(t, log.String(), "children count")
|
||||
})
|
||||
|
||||
nodeAA := treeA.Factory(context.Background(), kindCompareNode)
|
||||
nodeAA.SetID(id.NewNodeID("levelone"))
|
||||
nodeA.SetChild(nodeAA)
|
||||
|
||||
nodeB := treeB.Factory(context.Background(), kindCompareNode)
|
||||
nodeB.SetID(id.NewNodeID("root"))
|
||||
|
||||
t.Run("children are the same", func(t *testing.T) {
|
||||
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
|
||||
nodeBB.SetID(id.NewNodeID("levelone"))
|
||||
nodeB.SetChild(nodeBB)
|
||||
|
||||
assert.True(t, NodeCompare(context.Background(), nodeA, nodeB))
|
||||
|
||||
nodeB.DeleteChild(nodeBB.GetID())
|
||||
})
|
||||
|
||||
t.Run("children have different IDs", func(t *testing.T) {
|
||||
log.Reset()
|
||||
|
||||
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
|
||||
nodeBB.SetID(id.NewNodeID("SOMETHINGELSE"))
|
||||
nodeB.SetChild(nodeBB)
|
||||
|
||||
assert.False(t, NodeCompare(context.Background(), nodeA, nodeB))
|
||||
assert.Contains(t, log.String(), "id levelone matching the child")
|
||||
|
||||
nodeB.DeleteChild(nodeBB.GetID())
|
||||
})
|
||||
|
||||
t.Run("children have different content", func(t *testing.T) {
|
||||
log.Reset()
|
||||
|
||||
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
|
||||
nodeBB.FromFormat(&compareFormat{V: 12345678})
|
||||
nodeBB.SetID(id.NewNodeID("levelone"))
|
||||
nodeB.SetChild(nodeBB)
|
||||
|
||||
assert.False(t, NodeCompare(context.Background(), nodeA, nodeB))
|
||||
assert.Contains(t, log.String(), "difference")
|
||||
assert.Contains(t, log.String(), "12345678")
|
||||
|
||||
nodeB.DeleteChild(nodeBB.GetID())
|
||||
})
|
||||
|
||||
t.Run("children are the same because of their mapped ID", func(t *testing.T) {
|
||||
log.Reset()
|
||||
|
||||
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
|
||||
nodeBB.SetID(id.NewNodeID("REMAPPEDID"))
|
||||
nodeB.SetChild(nodeBB)
|
||||
|
||||
nodeAA.SetMappedID(id.NewNodeID("REMAPPEDID"))
|
||||
|
||||
assert.True(t, NodeCompare(context.Background(), nodeA, nodeB))
|
||||
|
||||
nodeB.DeleteChild(nodeBB.GetID())
|
||||
nodeAA.SetMappedID(id.NilID)
|
||||
})
|
||||
|
||||
t.Run("children are different because of their mapped ID", func(t *testing.T) {
|
||||
log.Reset()
|
||||
|
||||
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
|
||||
nodeBB.SetID(id.NewNodeID("levelone"))
|
||||
nodeB.SetChild(nodeBB)
|
||||
|
||||
nodeAA.SetMappedID(id.NewNodeID("REMAPPEDID"))
|
||||
|
||||
assert.False(t, NodeCompare(context.Background(), nodeA, nodeB))
|
||||
assert.Contains(t, log.String(), "id REMAPPEDID matching the child")
|
||||
|
||||
nodeB.DeleteChild(nodeBB.GetID())
|
||||
nodeAA.SetMappedID(id.NilID)
|
||||
})
|
||||
}
|
107
tree/generic/driver_node.go
Normal file
107
tree/generic/driver_node.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
)
|
||||
|
||||
type NodeDriverInterface interface {
|
||||
logger.MessageInterface
|
||||
|
||||
MapIDInterface
|
||||
|
||||
IsNull() bool
|
||||
|
||||
GetNode() NodeInterface
|
||||
SetNode(NodeInterface)
|
||||
|
||||
SetTreeDriver(treeDriver TreeDriverInterface)
|
||||
GetTreeDriver() TreeDriverInterface
|
||||
|
||||
ListPage(context.Context, int) ChildrenSlice
|
||||
GetIDFromName(context.Context, string) id.NodeID
|
||||
|
||||
Equals(context.Context, NodeInterface) bool
|
||||
|
||||
Get(context.Context) bool
|
||||
Put(context.Context) id.NodeID
|
||||
Patch(context.Context)
|
||||
Delete(context.Context)
|
||||
|
||||
NewFormat() f3.Interface
|
||||
FromFormat(f3.Interface)
|
||||
ToFormat() f3.Interface
|
||||
|
||||
LookupMappedID(id.NodeID) id.NodeID
|
||||
|
||||
String() string
|
||||
}
|
||||
|
||||
type NullDriver struct {
|
||||
logger.Logger
|
||||
|
||||
node NodeInterface
|
||||
mapped id.NodeID
|
||||
treeDriver TreeDriverInterface
|
||||
}
|
||||
|
||||
func NewNullDriver() NodeDriverInterface {
|
||||
return &NullDriver{}
|
||||
}
|
||||
|
||||
func (o *NullDriver) IsNull() bool { return true }
|
||||
func (o *NullDriver) SetNode(node NodeInterface) { o.node = node }
|
||||
func (o *NullDriver) GetNode() NodeInterface { return o.node }
|
||||
func (o *NullDriver) GetMappedID() id.NodeID {
|
||||
if o.mapped == nil {
|
||||
return id.NilID
|
||||
}
|
||||
return o.mapped
|
||||
}
|
||||
func (o *NullDriver) SetMappedID(mapped id.NodeID) { o.mapped = mapped }
|
||||
func (o *NullDriver) SetTreeDriver(treeDriver TreeDriverInterface) {
|
||||
o.treeDriver = treeDriver
|
||||
if treeDriver != nil {
|
||||
o.SetLogger(treeDriver)
|
||||
}
|
||||
}
|
||||
func (o *NullDriver) GetTreeDriver() TreeDriverInterface { return o.treeDriver }
|
||||
func (o *NullDriver) ListPage(context.Context, int) ChildrenSlice {
|
||||
panic(fmt.Errorf("ListPage %s", o.GetNode().GetKind()))
|
||||
}
|
||||
|
||||
func (o *NullDriver) GetIDFromName(ctx context.Context, name string) id.NodeID {
|
||||
panic(errors.New(name))
|
||||
}
|
||||
|
||||
func (o *NullDriver) Equals(context.Context, NodeInterface) bool {
|
||||
panic(fmt.Errorf("Equals %s", o.GetNode().GetKind()))
|
||||
}
|
||||
func (o *NullDriver) Get(context.Context) bool { panic(fmt.Errorf("Get %s", o.GetNode().GetKind())) }
|
||||
func (o *NullDriver) Put(context.Context) id.NodeID {
|
||||
panic(fmt.Errorf("Put %s", o.GetNode().GetKind()))
|
||||
}
|
||||
|
||||
func (o *NullDriver) Patch(context.Context) {
|
||||
panic(fmt.Errorf("Patch %s", o.GetNode().GetKind()))
|
||||
}
|
||||
func (o *NullDriver) Delete(context.Context) { panic(fmt.Errorf("Delete %s", o.GetNode().GetKind())) }
|
||||
func (o *NullDriver) NewFormat() f3.Interface {
|
||||
panic(fmt.Errorf("NewFormat %s", o.GetNode().GetKind()))
|
||||
}
|
||||
|
||||
func (o *NullDriver) FromFormat(f3.Interface) {
|
||||
panic(fmt.Errorf("FromFormat %s", o.GetNode().GetKind()))
|
||||
}
|
||||
func (o *NullDriver) ToFormat() f3.Interface { panic(fmt.Errorf("ToFormat %s", o.GetNode().GetKind())) }
|
||||
func (o *NullDriver) LookupMappedID(id.NodeID) id.NodeID { return id.NilID }
|
||||
func (o *NullDriver) String() string { return "" }
|
78
tree/generic/driver_tree.go
Normal file
78
tree/generic/driver_tree.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
type TreeDriverInterface interface {
|
||||
logger.Interface
|
||||
|
||||
GetTree() TreeInterface
|
||||
SetTree(TreeInterface)
|
||||
|
||||
GetPageSize() int
|
||||
SetPageSize(int)
|
||||
|
||||
AllocateID() bool
|
||||
|
||||
Init()
|
||||
|
||||
Diff(a, b NodeDriverInterface) string
|
||||
|
||||
Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface
|
||||
}
|
||||
|
||||
var DefaultPageSize = int(25)
|
||||
|
||||
type NullTreeDriver struct {
|
||||
logger.Logger
|
||||
|
||||
tree TreeInterface
|
||||
|
||||
pageSize int
|
||||
}
|
||||
|
||||
func (o *NullTreeDriver) Init() {
|
||||
o.pageSize = DefaultPageSize
|
||||
}
|
||||
|
||||
func NewNullTreeDriver() TreeDriverInterface {
|
||||
d := &NullTreeDriver{}
|
||||
d.Init()
|
||||
return d
|
||||
}
|
||||
|
||||
func (o *NullTreeDriver) SetTree(tree TreeInterface) {
|
||||
o.tree = tree
|
||||
if tree != nil {
|
||||
o.SetLogger(tree)
|
||||
}
|
||||
}
|
||||
func (o *NullTreeDriver) GetTree() TreeInterface { return o.tree }
|
||||
|
||||
func (o *NullTreeDriver) GetPageSize() int { return o.pageSize }
|
||||
func (o *NullTreeDriver) SetPageSize(pageSize int) { o.pageSize = pageSize }
|
||||
|
||||
func (o *NullTreeDriver) AllocateID() bool { return true }
|
||||
|
||||
func (o *NullTreeDriver) Diff(a, b NodeDriverInterface) string {
|
||||
aFormat := a.ToFormat()
|
||||
bFormat := b.ToFormat()
|
||||
|
||||
return cmp.Diff(aFormat, bFormat)
|
||||
}
|
||||
|
||||
func (o *NullTreeDriver) Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface {
|
||||
d := NewNullDriver()
|
||||
d.SetTreeDriver(o)
|
||||
return d
|
||||
}
|
31
tree/generic/factory.go
Normal file
31
tree/generic/factory.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/options"
|
||||
)
|
||||
|
||||
var treeFactories = make(map[string]TreeFactory, 10)
|
||||
|
||||
type TreeFactory func(ctx context.Context, opts options.Interface) TreeInterface
|
||||
|
||||
func RegisterFactory(name string, factory TreeFactory) {
|
||||
name = strings.ToLower(name)
|
||||
treeFactories[name] = factory
|
||||
}
|
||||
|
||||
func GetFactory(name string) TreeFactory {
|
||||
name = strings.ToLower(name)
|
||||
factory, ok := treeFactories[name]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("no factory registered for %s", name))
|
||||
}
|
||||
return factory
|
||||
}
|
101
tree/generic/interface_node.go
Normal file
101
tree/generic/interface_node.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
)
|
||||
|
||||
type NodeAccessorsInterface interface {
|
||||
SetIsNil(bool)
|
||||
GetIsNil() bool
|
||||
|
||||
SetIsSync(bool)
|
||||
GetIsSync() bool
|
||||
|
||||
GetParent() NodeInterface
|
||||
SetParent(NodeInterface)
|
||||
|
||||
GetKind() kind.Kind
|
||||
SetKind(kind.Kind)
|
||||
|
||||
GetID() id.NodeID
|
||||
SetID(id.NodeID)
|
||||
|
||||
GetTree() TreeInterface
|
||||
SetTree(TreeInterface)
|
||||
|
||||
GetNodeChildren() NodeChildren
|
||||
GetChildren() ChildrenSlice
|
||||
SetChildren(NodeChildren)
|
||||
|
||||
GetDriver() NodeDriverInterface
|
||||
SetDriver(NodeDriverInterface)
|
||||
}
|
||||
|
||||
type NodeTreeInterface interface {
|
||||
GetChild(id.NodeID) NodeInterface
|
||||
GetIDFromName(context.Context, string) id.NodeID
|
||||
SetChild(NodeInterface) NodeInterface
|
||||
DeleteChild(id.NodeID) NodeInterface
|
||||
CreateChild(context.Context) NodeInterface
|
||||
|
||||
MustFind(path.Path) NodeInterface
|
||||
Find(path.Path) NodeInterface
|
||||
FindAndGet(context.Context, path.Path) NodeInterface
|
||||
|
||||
GetCurrentPath() path.Path
|
||||
|
||||
Walk(ctx context.Context, parent path.Path, options *WalkOptions)
|
||||
WalkAndGet(ctx context.Context, parent path.Path, options *WalkOptions)
|
||||
Apply(ctx context.Context, parent, path path.Path, options *ApplyOptions) bool
|
||||
ApplyAndGet(ctx context.Context, path path.Path, options *ApplyOptions) bool
|
||||
|
||||
List(context.Context) ChildrenSlice
|
||||
}
|
||||
|
||||
type NodeDriverProxyInterface interface {
|
||||
MapIDInterface
|
||||
ListPage(context.Context, int) ChildrenSlice
|
||||
GetIDFromName(context.Context, string) id.NodeID
|
||||
|
||||
Equals(context.Context, NodeInterface) bool
|
||||
|
||||
Get(context.Context) NodeInterface
|
||||
Upsert(context.Context) NodeInterface
|
||||
Delete(context.Context) NodeInterface
|
||||
|
||||
NewFormat() f3.Interface
|
||||
FromFormat(f3.Interface) NodeInterface
|
||||
ToFormat() f3.Interface
|
||||
|
||||
LookupMappedID(id.NodeID) id.NodeID
|
||||
}
|
||||
|
||||
type MapIDInterface interface {
|
||||
GetMappedID() id.NodeID
|
||||
SetMappedID(id.NodeID)
|
||||
}
|
||||
|
||||
type NodeInterface interface {
|
||||
logger.MessageInterface
|
||||
NodeAccessorsInterface
|
||||
NodeTreeInterface
|
||||
NodeDriverProxyInterface
|
||||
path.PathElement
|
||||
|
||||
Init(NodeInterface) NodeInterface
|
||||
|
||||
GetSelf() NodeInterface
|
||||
SetSelf(NodeInterface)
|
||||
|
||||
String() string
|
||||
}
|
58
tree/generic/interface_tree.go
Normal file
58
tree/generic/interface_tree.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
"code.forgejo.org/f3/gof3/v3/options"
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
)
|
||||
|
||||
type TreeInterface interface {
|
||||
logger.Interface
|
||||
|
||||
Init(TreeInterface, options.Interface) TreeInterface
|
||||
|
||||
GetOptions() options.Interface
|
||||
SetOptions(options.Interface)
|
||||
|
||||
GetSelf() TreeInterface
|
||||
SetSelf(TreeInterface)
|
||||
|
||||
SetRoot(NodeInterface)
|
||||
GetRoot() NodeInterface
|
||||
|
||||
SetLogger(logger.Interface)
|
||||
GetLogger() logger.Interface
|
||||
|
||||
GetDriver() TreeDriverInterface
|
||||
SetDriver(TreeDriverInterface)
|
||||
|
||||
GetChildrenKind(kind.Kind) kind.Kind
|
||||
|
||||
GetPageSize() int
|
||||
|
||||
AllocateID() bool
|
||||
|
||||
Clear(context.Context)
|
||||
|
||||
Diff(a, b NodeInterface) string
|
||||
|
||||
MustFind(path.Path) NodeInterface
|
||||
Find(path.Path) NodeInterface
|
||||
FindAndGet(context.Context, path.Path) NodeInterface
|
||||
Exists(context.Context, path.Path) bool
|
||||
|
||||
Walk(context.Context, *WalkOptions)
|
||||
WalkAndGet(context.Context, *WalkOptions)
|
||||
Apply(context.Context, path.Path, *ApplyOptions) bool
|
||||
ApplyAndGet(context.Context, path.Path, *ApplyOptions) bool
|
||||
|
||||
Register(kind kind.Kind, factory FactoryFun)
|
||||
Factory(ctx context.Context, kind kind.Kind) NodeInterface
|
||||
}
|
139
tree/generic/main_test.go
Normal file
139
tree/generic/main_test.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
"code.forgejo.org/f3/gof3/v3/options"
|
||||
options_logger "code.forgejo.org/f3/gof3/v3/options/logger"
|
||||
)
|
||||
|
||||
type Storage map[string]int
|
||||
|
||||
type testTree struct {
|
||||
Tree
|
||||
}
|
||||
|
||||
type testOptions struct {
|
||||
options.Options
|
||||
options_logger.OptionsLogger
|
||||
}
|
||||
|
||||
func newTestOptions() options.Interface {
|
||||
opts := &testOptions{}
|
||||
opts.SetName("test")
|
||||
l := logger.NewLogger()
|
||||
l.SetLevel(logger.Trace)
|
||||
opts.SetLogger(l)
|
||||
return opts
|
||||
}
|
||||
|
||||
type noopNodeDriver struct {
|
||||
NullDriver
|
||||
}
|
||||
|
||||
func (o *noopNodeDriver) ListPage(context.Context, int) ChildrenSlice {
|
||||
return ChildrenSlice{}
|
||||
}
|
||||
|
||||
func (o *noopNodeDriver) GetIDFromName(ctx context.Context, name string) id.NodeID {
|
||||
return id.NilID
|
||||
}
|
||||
|
||||
func (o *noopNodeDriver) Equals(context.Context, NodeInterface) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *noopNodeDriver) Put(context.Context) id.NodeID {
|
||||
return o.GetNode().GetID()
|
||||
}
|
||||
|
||||
func (o *noopNodeDriver) Patch(context.Context) {
|
||||
}
|
||||
|
||||
func (o *noopNodeDriver) NewFormat() f3.Interface {
|
||||
f := f3.NewCommon(id.NilID.String())
|
||||
return &f
|
||||
}
|
||||
|
||||
func (o *noopNodeDriver) ToFormat() f3.Interface {
|
||||
f := f3.NewCommon(o.GetNode().GetID().String())
|
||||
return &f
|
||||
}
|
||||
|
||||
func (o *noopNodeDriver) FromFormat(f f3.Interface) {
|
||||
o.GetNode().SetID(id.NewNodeID(f.GetID()))
|
||||
}
|
||||
|
||||
func newTestNodeDriver() NodeDriverInterface {
|
||||
return &noopNodeDriver{}
|
||||
}
|
||||
|
||||
type testTreeDriver struct {
|
||||
NullTreeDriver
|
||||
}
|
||||
|
||||
func newTestTreeDriver() TreeDriverInterface {
|
||||
return &testTreeDriver{}
|
||||
}
|
||||
|
||||
func (o *testTreeDriver) Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface {
|
||||
d := newTestNodeDriver()
|
||||
d.SetTreeDriver(o)
|
||||
return d
|
||||
}
|
||||
|
||||
func newTestTree() TreeInterface {
|
||||
tree := &testTree{}
|
||||
tree.Init(tree, newTestOptions())
|
||||
tree.Trace("init done")
|
||||
tree.SetDriver(newTestTreeDriver())
|
||||
tree.Register(kindTestNodeLevelOne, func(ctx context.Context, kind kind.Kind) NodeInterface {
|
||||
node := &testNodeLevelOne{}
|
||||
return node.Init(node)
|
||||
})
|
||||
tree.Register(kindTestNodeLevelTwo, func(ctx context.Context, kind kind.Kind) NodeInterface {
|
||||
node := &testNodeLevelTwo{}
|
||||
return node.Init(node)
|
||||
})
|
||||
return tree
|
||||
}
|
||||
|
||||
type testNodeInterface interface {
|
||||
GetV() int
|
||||
}
|
||||
|
||||
type testNode struct {
|
||||
Node
|
||||
v int
|
||||
}
|
||||
|
||||
func (o *testNode) GetV() int {
|
||||
return o.v
|
||||
}
|
||||
|
||||
func (o *testNode) Equals(ctx context.Context, other NodeInterface) bool {
|
||||
if !o.Node.Equals(ctx, other) {
|
||||
return false
|
||||
}
|
||||
return o.GetV() == other.(testNodeInterface).GetV()
|
||||
}
|
||||
|
||||
var kindTestNodeLevelOne = kind.Kind("levelone")
|
||||
|
||||
type testNodeLevelOne struct {
|
||||
testNode
|
||||
}
|
||||
|
||||
var kindTestNodeLevelTwo = kind.Kind("leveltwo")
|
||||
|
||||
type testNodeLevelTwo struct {
|
||||
testNode
|
||||
}
|
52
tree/generic/mirror.go
Normal file
52
tree/generic/mirror.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
)
|
||||
|
||||
type MirrorOptions struct {
|
||||
noremap bool
|
||||
}
|
||||
|
||||
func NewMirrorOptions() *MirrorOptions {
|
||||
return &MirrorOptions{}
|
||||
}
|
||||
|
||||
func (o *MirrorOptions) SetNoRemap(noremap bool) *MirrorOptions {
|
||||
o.noremap = noremap
|
||||
return o
|
||||
}
|
||||
|
||||
func NodeMirror(ctx context.Context, origin, destination NodeInterface, options *MirrorOptions) {
|
||||
origin.Trace("origin '%s' | destination '%s'", origin.GetCurrentPath().ReadableString(), destination.GetCurrentPath().ReadableString())
|
||||
origin.Trace("start unify references")
|
||||
for _, reference := range NodeCollectReferences(ctx, origin) {
|
||||
origin.Trace("unify reference %s", reference)
|
||||
TreeUnifyPath(ctx, origin.GetTree(), reference, destination.GetTree(), NewUnifyOptions(destination.GetTree()))
|
||||
}
|
||||
origin.Trace("end unify references")
|
||||
originParents := origin.GetParent().GetCurrentPath()
|
||||
destinationParents := destination.GetParent().GetCurrentPath()
|
||||
NodeUnify(ctx, origin, originParents, destination, destinationParents, NewUnifyOptions(destination.GetTree()))
|
||||
}
|
||||
|
||||
func TreeMirror(ctx context.Context, originTree, destinationTree TreeInterface, path path.Path, options *MirrorOptions) {
|
||||
originTree.Trace("'%s'", path.ReadableString())
|
||||
TreeUnifyPath(ctx, originTree, path, destinationTree, NewUnifyOptions(destinationTree))
|
||||
TreeParallelApply(ctx, originTree, path, destinationTree, NewParallelApplyOptions(func(ctx context.Context, origin, destination NodeInterface) {
|
||||
NodeMirror(ctx, origin, destination, NewMirrorOptions())
|
||||
}))
|
||||
}
|
||||
|
||||
func TreePartialMirror(ctx context.Context, originTree TreeInterface, originPath path.Path, destinationTree TreeInterface, destinationPath path.Path, options *MirrorOptions) {
|
||||
originTree.Trace("'%s => %s'", originPath, destinationPath)
|
||||
originNode := originTree.MustFind(originPath)
|
||||
destinationNode := destinationTree.MustFind(destinationPath)
|
||||
NodeMirror(ctx, originNode, destinationNode, NewMirrorOptions())
|
||||
}
|
414
tree/generic/node.go
Normal file
414
tree/generic/node.go
Normal file
|
@ -0,0 +1,414 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
"code.forgejo.org/f3/gof3/v3/util"
|
||||
)
|
||||
|
||||
type ChildrenSlice []NodeInterface
|
||||
|
||||
func NewChildrenSlice(len int) ChildrenSlice {
|
||||
return make([]NodeInterface, 0, len)
|
||||
}
|
||||
|
||||
func (o ChildrenSlice) Len() int {
|
||||
return len(o)
|
||||
}
|
||||
|
||||
func (o ChildrenSlice) Swap(i, j int) {
|
||||
o[i], o[j] = o[j], o[i]
|
||||
}
|
||||
|
||||
func (o ChildrenSlice) Less(i, j int) bool {
|
||||
return o[i].GetID().String() < o[j].GetID().String()
|
||||
}
|
||||
|
||||
type NodeChildren map[id.NodeID]NodeInterface
|
||||
|
||||
func NewNodeChildren() NodeChildren {
|
||||
return make(NodeChildren)
|
||||
}
|
||||
|
||||
var (
|
||||
NilNode = &Node{isNil: true}
|
||||
NilParent = NilNode
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
logger.Logger
|
||||
|
||||
tree TreeInterface
|
||||
isNil bool
|
||||
sync bool
|
||||
kind kind.Kind
|
||||
id id.NodeID
|
||||
|
||||
driver NodeDriverInterface
|
||||
self NodeInterface
|
||||
parent NodeInterface
|
||||
|
||||
children NodeChildren
|
||||
}
|
||||
|
||||
func NewElementNode() path.PathElement {
|
||||
return NewNode().(path.PathElement)
|
||||
}
|
||||
|
||||
func NewNode() NodeInterface {
|
||||
node := &Node{}
|
||||
return node.Init(node)
|
||||
}
|
||||
|
||||
func NewNodeFromID[T any](i T) NodeInterface {
|
||||
node := NewNode()
|
||||
node.SetID(id.NewNodeID(i))
|
||||
return node
|
||||
}
|
||||
|
||||
func (o *Node) Init(self NodeInterface) NodeInterface {
|
||||
o.SetTree(nil)
|
||||
o.SetIsNil(true)
|
||||
o.SetKind(kind.KindNil)
|
||||
o.SetID(id.NilID)
|
||||
o.SetSelf(self)
|
||||
o.SetParent(NilNode)
|
||||
o.children = NewNodeChildren()
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (o *Node) GetIsNil() bool { return o == nil || o.isNil }
|
||||
func (o *Node) SetIsNil(isNil bool) { o.isNil = isNil }
|
||||
|
||||
func (o *Node) GetIsSync() bool { return o.sync }
|
||||
func (o *Node) SetIsSync(sync bool) { o.sync = sync }
|
||||
|
||||
func (o *Node) GetSelf() NodeInterface { return o.self }
|
||||
func (o *Node) SetSelf(self NodeInterface) { o.self = self }
|
||||
|
||||
func (o *Node) GetParent() NodeInterface { return o.parent }
|
||||
func (o *Node) SetParent(parent NodeInterface) { o.parent = parent }
|
||||
|
||||
func (o *Node) GetKind() kind.Kind { return o.kind }
|
||||
func (o *Node) SetKind(kind kind.Kind) { o.kind = kind }
|
||||
|
||||
func (o *Node) GetID() id.NodeID {
|
||||
if o.id == nil {
|
||||
return id.NilID
|
||||
}
|
||||
return o.id
|
||||
}
|
||||
func (o *Node) SetID(id id.NodeID) { o.id = id }
|
||||
|
||||
func (o *Node) GetMappedID() id.NodeID {
|
||||
mappedID := o.GetDriver().GetMappedID()
|
||||
if mappedID == nil {
|
||||
mappedID = id.NilID
|
||||
}
|
||||
return mappedID
|
||||
}
|
||||
|
||||
func (o *Node) SetMappedID(mapped id.NodeID) {
|
||||
o.GetDriver().SetMappedID(mapped)
|
||||
}
|
||||
|
||||
func (o *Node) GetTree() TreeInterface { return o.tree }
|
||||
func (o *Node) SetTree(tree TreeInterface) {
|
||||
o.tree = tree
|
||||
if tree != nil {
|
||||
o.SetLogger(tree.GetLogger())
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Node) GetDriver() NodeDriverInterface { return o.driver }
|
||||
func (o *Node) SetDriver(driver NodeDriverInterface) {
|
||||
driver.SetNode(o)
|
||||
o.driver = driver
|
||||
}
|
||||
|
||||
func (o *Node) GetNodeChildren() NodeChildren {
|
||||
return o.children
|
||||
}
|
||||
|
||||
func (o *Node) GetChildren() ChildrenSlice {
|
||||
children := NewChildrenSlice(len(o.children))
|
||||
for _, child := range o.children {
|
||||
children = append(children, child)
|
||||
}
|
||||
sort.Sort(children)
|
||||
return children
|
||||
}
|
||||
|
||||
func (o *Node) SetChildren(children NodeChildren) { o.children = children }
|
||||
|
||||
func (o *Node) String() string {
|
||||
driver := o.GetDriver().String()
|
||||
if driver != "" {
|
||||
driver = "=" + driver
|
||||
}
|
||||
return o.GetID().String() + driver
|
||||
}
|
||||
|
||||
func (o *Node) Equals(ctx context.Context, other NodeInterface) bool {
|
||||
return o.GetKind() == other.GetKind() && o.GetID() == other.GetID()
|
||||
}
|
||||
|
||||
func (o *Node) GetCurrentPath() path.Path {
|
||||
var p path.Path
|
||||
if o.GetIsNil() {
|
||||
p = path.NewPath()
|
||||
} else {
|
||||
p = o.GetParent().GetCurrentPath().Append(o)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (o *Node) ListPage(ctx context.Context, page int) ChildrenSlice {
|
||||
return o.GetDriver().ListPage(ctx, page)
|
||||
}
|
||||
|
||||
func (o *Node) List(ctx context.Context) ChildrenSlice {
|
||||
o.Trace("%s", o.GetKind())
|
||||
children := NewNodeChildren()
|
||||
|
||||
self := o.GetSelf()
|
||||
for page := 1; ; page++ {
|
||||
util.MaybeTerminate(ctx)
|
||||
childrenPage := self.ListPage(ctx, page)
|
||||
for _, child := range childrenPage {
|
||||
children[child.GetID()] = child
|
||||
}
|
||||
|
||||
if len(childrenPage) < o.GetTree().GetPageSize() {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
o.children = children
|
||||
return o.GetChildren()
|
||||
}
|
||||
|
||||
func (o *Node) GetChild(id id.NodeID) NodeInterface {
|
||||
child, ok := o.children[id]
|
||||
if !ok {
|
||||
return NilNode
|
||||
}
|
||||
return child
|
||||
}
|
||||
|
||||
func (o *Node) SetChild(child NodeInterface) NodeInterface {
|
||||
o.children[child.GetID()] = child
|
||||
return child
|
||||
}
|
||||
|
||||
func (o *Node) DeleteChild(id id.NodeID) NodeInterface {
|
||||
if child, ok := o.children[id]; ok {
|
||||
delete(o.children, id)
|
||||
return child
|
||||
}
|
||||
return NilNode
|
||||
}
|
||||
|
||||
func (o *Node) CreateChild(ctx context.Context) NodeInterface {
|
||||
tree := o.GetTree()
|
||||
child := tree.Factory(ctx, tree.GetChildrenKind(o.GetKind()))
|
||||
child.SetParent(o)
|
||||
return child
|
||||
}
|
||||
|
||||
func (o *Node) GetIDFromName(ctx context.Context, name string) id.NodeID {
|
||||
return o.GetDriver().GetIDFromName(ctx, name)
|
||||
}
|
||||
|
||||
func (o *Node) Get(ctx context.Context) NodeInterface {
|
||||
if o.GetDriver().Get(ctx) {
|
||||
o.SetIsSync(true)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *Node) Upsert(ctx context.Context) NodeInterface {
|
||||
if o.GetID() != id.NilID {
|
||||
o.GetDriver().Patch(ctx)
|
||||
} else {
|
||||
o.SetID(o.GetDriver().Put(ctx))
|
||||
}
|
||||
if o.GetParent() != NilNode {
|
||||
o.GetParent().SetChild(o)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *Node) Delete(ctx context.Context) NodeInterface {
|
||||
o.GetDriver().Delete(ctx)
|
||||
return o.GetParent().DeleteChild(o.GetID())
|
||||
}
|
||||
|
||||
func (o *Node) NewFormat() f3.Interface {
|
||||
return o.GetDriver().NewFormat()
|
||||
}
|
||||
|
||||
func (o *Node) FromFormat(f f3.Interface) NodeInterface {
|
||||
o.SetID(id.NewNodeID(f.GetID()))
|
||||
o.GetDriver().FromFormat(f)
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *Node) ToFormat() f3.Interface {
|
||||
if o == nil || o.GetDriver() == nil {
|
||||
return nil
|
||||
}
|
||||
return o.GetDriver().ToFormat()
|
||||
}
|
||||
|
||||
func (o *Node) LookupMappedID(id id.NodeID) id.NodeID { return o.GetDriver().LookupMappedID(id) }
|
||||
|
||||
func (o *Node) ApplyAndGet(ctx context.Context, p path.Path, options *ApplyOptions) bool {
|
||||
applyWrapInGet := func(options *ApplyOptions) ApplyFunc {
|
||||
return func(ctx context.Context, parent, p path.Path, node NodeInterface) {
|
||||
if options.fun != nil && (p.Empty() || options.where == ApplyEachNode) {
|
||||
options.fun(ctx, parent, p, node)
|
||||
}
|
||||
if p.Empty() {
|
||||
return
|
||||
}
|
||||
|
||||
childID := p.First().(NodeInterface).GetID()
|
||||
// is it a known child?
|
||||
child := node.GetChild(childID)
|
||||
// if not, maybe it is a known child referenced by name
|
||||
if child.GetIsNil() {
|
||||
childIDFromName := node.GetIDFromName(ctx, childID.String())
|
||||
if childIDFromName != id.NilID {
|
||||
childID = childIDFromName
|
||||
child = node.GetChild(childID)
|
||||
p.First().(NodeInterface).SetID(childID)
|
||||
}
|
||||
}
|
||||
// not a known child by ID or by Name: make one
|
||||
if child.GetIsNil() {
|
||||
child = node.CreateChild(ctx)
|
||||
child.SetID(childID)
|
||||
if child.Get(ctx).GetIsSync() {
|
||||
// only set the child if the driver is able to get it, otherwise
|
||||
// it means that although the ID is known the child itself cannot be
|
||||
// obtained and is assumed to be not found
|
||||
node.SetChild(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return o.Apply(ctx, path.NewPath(), p, NewApplyOptions(applyWrapInGet(options)).SetWhere(ApplyEachNode))
|
||||
}
|
||||
|
||||
func (o *Node) WalkAndGet(ctx context.Context, parent path.Path, options *WalkOptions) {
|
||||
walkWrapInGet := func(fun WalkFunc) WalkFunc {
|
||||
return func(ctx context.Context, p path.Path, node NodeInterface) {
|
||||
node.Get(ctx)
|
||||
node.List(ctx)
|
||||
if fun != nil {
|
||||
fun(ctx, p, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
o.Walk(ctx, parent, NewWalkOptions(walkWrapInGet(options.fun)))
|
||||
}
|
||||
|
||||
func (o *Node) Walk(ctx context.Context, parent path.Path, options *WalkOptions) {
|
||||
util.MaybeTerminate(ctx)
|
||||
options.fun(ctx, parent, o.GetSelf())
|
||||
parent = parent.Append(o.GetSelf().(path.PathElement))
|
||||
for _, child := range o.GetSelf().GetChildren() {
|
||||
child.Walk(ctx, parent, options)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Node) FindAndGet(ctx context.Context, p path.Path) NodeInterface {
|
||||
var r NodeInterface
|
||||
r = NilNode
|
||||
set := func(ctx context.Context, parent, p path.Path, node NodeInterface) {
|
||||
r = node
|
||||
}
|
||||
o.ApplyAndGet(ctx, p, NewApplyOptions(set))
|
||||
return r
|
||||
}
|
||||
|
||||
func (o *Node) MustFind(p path.Path) NodeInterface {
|
||||
found := o.Find(p)
|
||||
if found.GetIsNil() {
|
||||
panic(fmt.Errorf("%s not found", p))
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func (o *Node) Find(p path.Path) NodeInterface {
|
||||
if p.Empty() {
|
||||
return o
|
||||
}
|
||||
|
||||
child := o.GetChild(p.First().(NodeInterface).GetID())
|
||||
if child.GetIsNil() {
|
||||
o.Info("'%s' not found", p.String())
|
||||
return NilNode
|
||||
}
|
||||
|
||||
return child.Find(p.RemoveFirst())
|
||||
}
|
||||
|
||||
func (o *Node) Apply(ctx context.Context, parentPath, p path.Path, options *ApplyOptions) bool {
|
||||
o.Trace("parent '%s', node '%s', path '%s'", parentPath.ReadableString(), o.String(), p.ReadableString())
|
||||
|
||||
util.MaybeTerminate(ctx)
|
||||
|
||||
if p.Empty() {
|
||||
options.fun(ctx, parentPath, p, o.GetSelf())
|
||||
return true
|
||||
}
|
||||
|
||||
i := p.First().(NodeInterface).GetID().String()
|
||||
|
||||
if i == "." {
|
||||
return o.Apply(ctx, parentPath, p.RemoveFirst(), options)
|
||||
}
|
||||
|
||||
if i == ".." {
|
||||
parent, parentPath := parentPath.Pop()
|
||||
return parent.(NodeInterface).Apply(ctx, parentPath, p.RemoveFirst(), options)
|
||||
}
|
||||
|
||||
if options.where == ApplyEachNode {
|
||||
options.fun(ctx, parentPath, p, o.GetSelf())
|
||||
}
|
||||
|
||||
child := o.GetChild(p.First().(NodeInterface).GetID())
|
||||
if child.GetIsNil() && options.search == ApplySearchByName {
|
||||
if childID := o.GetIDFromName(ctx, p.First().(NodeInterface).GetID().String()); childID != id.NilID {
|
||||
child = o.GetChild(childID)
|
||||
}
|
||||
}
|
||||
if child.GetIsNil() {
|
||||
return false
|
||||
}
|
||||
parentPath = parentPath.Append(o.GetSelf().(path.PathElement))
|
||||
return child.Apply(ctx, parentPath, p.RemoveFirst(), options)
|
||||
}
|
||||
|
||||
type ErrorNodeNotFound error
|
||||
|
||||
func NewError[T error](message string, args ...any) T {
|
||||
e := fmt.Errorf(message, args...)
|
||||
return e.(T)
|
||||
}
|
296
tree/generic/node_test.go
Normal file
296
tree/generic/node_test.go
Normal file
|
@ -0,0 +1,296 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNodeInit(t *testing.T) {
|
||||
node := NewNode()
|
||||
assert.NotNil(t, node)
|
||||
}
|
||||
|
||||
func TestNewNodeFromID(t *testing.T) {
|
||||
{
|
||||
i := "nodeID"
|
||||
node := NewNodeFromID(i)
|
||||
assert.Equal(t, id.NewNodeID(i), node.GetID())
|
||||
}
|
||||
{
|
||||
i := 123
|
||||
node := NewNodeFromID(i)
|
||||
assert.Equal(t, id.NewNodeID(fmt.Sprintf("%d", i)), node.GetID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeIsNil(t *testing.T) {
|
||||
node := NewNode()
|
||||
assert.True(t, node.GetIsNil())
|
||||
|
||||
node.SetIsNil(false)
|
||||
assert.False(t, node.GetIsNil())
|
||||
|
||||
node.SetIsNil(true)
|
||||
assert.True(t, node.GetIsNil())
|
||||
|
||||
var nilNode *Node
|
||||
assert.True(t, nilNode.GetIsNil())
|
||||
}
|
||||
|
||||
func TestNodeEquals(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
tree := newTestTree()
|
||||
one := tree.Factory(ctx, kindTestNodeLevelOne)
|
||||
one.(*testNodeLevelOne).v = 1
|
||||
assert.True(t, one.Equals(ctx, one))
|
||||
two := tree.Factory(ctx, kindTestNodeLevelOne)
|
||||
two.(*testNodeLevelOne).v = 2
|
||||
assert.False(t, one.Equals(ctx, two))
|
||||
}
|
||||
|
||||
func TestNodeParent(t *testing.T) {
|
||||
node := NewNode()
|
||||
assert.True(t, node.GetParent().GetIsNil())
|
||||
parent := NewNode()
|
||||
node.SetParent(parent)
|
||||
assert.True(t, parent == node.GetParent())
|
||||
}
|
||||
|
||||
func TestNodeKind(t *testing.T) {
|
||||
node := NewNode()
|
||||
assert.EqualValues(t, kind.KindNil, node.GetKind())
|
||||
kind := kind.Kind("something")
|
||||
node.SetKind(kind)
|
||||
assert.True(t, kind == node.GetKind())
|
||||
}
|
||||
|
||||
func TestNodeMappedID(t *testing.T) {
|
||||
node := NewNode()
|
||||
node.SetDriver(NewNullDriver())
|
||||
assert.EqualValues(t, id.NilID, node.GetMappedID())
|
||||
mapped := id.NewNodeID("mapped")
|
||||
node.SetMappedID(mapped)
|
||||
assert.True(t, mapped == node.GetMappedID())
|
||||
}
|
||||
|
||||
func TestNodeNewFormat(t *testing.T) {
|
||||
node := NewNode()
|
||||
node.SetDriver(NewNullDriver())
|
||||
|
||||
assert.Panics(t, func() { node.NewFormat() })
|
||||
}
|
||||
|
||||
func TestChildren(t *testing.T) {
|
||||
parent := NewNode()
|
||||
assert.Empty(t, parent.GetChildren())
|
||||
|
||||
id1 := id.NewNodeID("one")
|
||||
child1 := NewNode()
|
||||
child1.SetID(id1)
|
||||
parent.SetChild(child1)
|
||||
|
||||
id2 := id.NewNodeID("two")
|
||||
child2 := NewNode()
|
||||
child2.SetID(id2)
|
||||
parent.SetChild(child2)
|
||||
|
||||
children := parent.GetChildren()
|
||||
assert.Len(t, children, 2)
|
||||
assert.Equal(t, children[0].GetID(), id1)
|
||||
assert.Equal(t, children[1].GetID(), id2)
|
||||
|
||||
nodeChildren := parent.GetNodeChildren()
|
||||
assert.Len(t, nodeChildren, 2)
|
||||
delete(nodeChildren, id1)
|
||||
parent.SetChildren(nodeChildren)
|
||||
nodeChildren = parent.GetNodeChildren()
|
||||
assert.Len(t, nodeChildren, 1)
|
||||
}
|
||||
|
||||
func TestNodeID(t *testing.T) {
|
||||
node := NewNode()
|
||||
assert.EqualValues(t, id.NilID, node.GetID())
|
||||
i := id.NewNodeID("1234")
|
||||
node.SetID(i)
|
||||
assert.True(t, i == node.GetID())
|
||||
}
|
||||
|
||||
func TestNodeTree(t *testing.T) {
|
||||
node := NewNode()
|
||||
assert.Nil(t, node.GetTree())
|
||||
tree := NewTree(newTestOptions())
|
||||
node.SetTree(tree)
|
||||
assert.True(t, tree == node.GetTree())
|
||||
}
|
||||
|
||||
type driverChildren struct {
|
||||
NullDriver
|
||||
}
|
||||
|
||||
func (o *driverChildren) ListPage(cxt context.Context, page int) ChildrenSlice {
|
||||
node := o.GetNode()
|
||||
tree := node.GetTree()
|
||||
count := tree.GetPageSize()
|
||||
if page > 1 {
|
||||
count = 1
|
||||
}
|
||||
|
||||
offset := (page - 1) * tree.GetPageSize()
|
||||
children := NewChildrenSlice(0)
|
||||
for i := 0; i < count; i++ {
|
||||
child := tree.GetSelf().Factory(cxt, kind.KindNil)
|
||||
child.SetID(id.NewNodeID(i + offset))
|
||||
children = append(children, child)
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
type driverTreeChildren struct {
|
||||
NullTreeDriver
|
||||
}
|
||||
|
||||
func (o *driverTreeChildren) Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface {
|
||||
return &driverChildren{}
|
||||
}
|
||||
|
||||
type treeChildren struct {
|
||||
Tree
|
||||
}
|
||||
|
||||
func newTreeChildren() TreeInterface {
|
||||
tree := &treeChildren{}
|
||||
tree.Init(tree, newTestOptions())
|
||||
treeDriver := &driverTreeChildren{}
|
||||
treeDriver.Init()
|
||||
tree.SetDriver(treeDriver)
|
||||
tree.Register(kind.KindNil, func(ctx context.Context, kind kind.Kind) NodeInterface {
|
||||
node := NewNode()
|
||||
node.SetIsNil(false)
|
||||
return node
|
||||
})
|
||||
return tree
|
||||
}
|
||||
|
||||
func TestNodeListPage(t *testing.T) {
|
||||
tree := newTreeChildren()
|
||||
|
||||
ctx := context.Background()
|
||||
node := tree.Factory(ctx, kind.KindNil)
|
||||
|
||||
children := node.ListPage(context.Background(), int(1))
|
||||
assert.NotNil(t, children)
|
||||
assert.EqualValues(t, tree.GetPageSize(), len(children))
|
||||
|
||||
children = node.ListPage(context.Background(), int(2))
|
||||
assert.NotNil(t, children)
|
||||
assert.EqualValues(t, 1, len(children))
|
||||
}
|
||||
|
||||
func TestNodeList(t *testing.T) {
|
||||
tree := newTreeChildren()
|
||||
|
||||
ctx := context.Background()
|
||||
node := tree.Factory(ctx, kind.KindNil)
|
||||
children := node.List(ctx)
|
||||
assert.EqualValues(t, tree.GetPageSize()+1, len(children))
|
||||
|
||||
idThree := id.NewNodeID("3")
|
||||
childThree := node.GetChild(idThree)
|
||||
assert.False(t, childThree.GetIsNil())
|
||||
assert.EqualValues(t, idThree, childThree.GetID())
|
||||
node.DeleteChild(idThree)
|
||||
childThree = node.GetChild(idThree)
|
||||
assert.True(t, childThree.GetIsNil())
|
||||
}
|
||||
|
||||
type nodeGetIDFromName struct {
|
||||
Node
|
||||
name string
|
||||
}
|
||||
|
||||
func (o *nodeGetIDFromName) GetIDFromName(ctx context.Context, name string) id.NodeID {
|
||||
for _, child := range o.GetChildren() {
|
||||
if child.(*nodeGetIDFromName).name == name {
|
||||
return child.GetID()
|
||||
}
|
||||
}
|
||||
return id.NilID
|
||||
}
|
||||
|
||||
func newNodeGetIDFromName(i, name string) NodeInterface {
|
||||
node := &nodeGetIDFromName{}
|
||||
node.Init(node)
|
||||
node.SetIsNil(false)
|
||||
node.SetID(id.NewNodeID(i))
|
||||
node.name = name
|
||||
return node
|
||||
}
|
||||
|
||||
func TestNodeGetIDFromName(t *testing.T) {
|
||||
i := "1243"
|
||||
name := "NAME"
|
||||
node := newNodeGetIDFromName(i, name)
|
||||
|
||||
ctx := context.Background()
|
||||
parent := newNodeGetIDFromName("parent", "PARENT")
|
||||
parent.SetChild(node)
|
||||
|
||||
r := parent.GetIDFromName(ctx, "OTHERNAME")
|
||||
assert.EqualValues(t, id.NilID, r)
|
||||
|
||||
r = parent.GetIDFromName(ctx, name)
|
||||
assert.True(t, r == id.NewNodeID(i))
|
||||
}
|
||||
|
||||
func TestNodePath(t *testing.T) {
|
||||
{
|
||||
path := NilNode.GetCurrentPath()
|
||||
assert.True(t, path.Empty())
|
||||
pathString := path.PathString()
|
||||
assert.True(t, pathString.Empty())
|
||||
assert.EqualValues(t, "", pathString.Join())
|
||||
}
|
||||
|
||||
root := NewNode()
|
||||
root.SetIsNil(false)
|
||||
root.SetKind(kind.KindRoot)
|
||||
|
||||
level1 := NewNode()
|
||||
level1.SetIsNil(false)
|
||||
level1.SetParent(root)
|
||||
id1 := id.NewNodeID("1")
|
||||
level1.SetID(id1)
|
||||
|
||||
level2 := NewNode()
|
||||
level2.SetIsNil(false)
|
||||
level2.SetParent(level1)
|
||||
id2 := id.NewNodeID("2")
|
||||
level2.SetID(id2)
|
||||
|
||||
{
|
||||
p := level2.GetCurrentPath()
|
||||
assert.False(t, p.Empty())
|
||||
if assert.EqualValues(t, 3, p.Length()) {
|
||||
assert.True(t, root == p.First(), p.First().(NodeInterface).GetID())
|
||||
rest := p.RemoveFirst()
|
||||
assert.True(t, level1 == rest.First(), p.First().(NodeInterface).GetID())
|
||||
rest = rest.RemoveFirst()
|
||||
assert.True(t, level2 == rest.First(), p.First().(NodeInterface).GetID())
|
||||
}
|
||||
pathString := p.PathString()
|
||||
assert.False(t, pathString.Empty())
|
||||
assert.EqualValues(t, 3, len(pathString.Elements()))
|
||||
assert.EqualValues(t, "/1/2", pathString.Join())
|
||||
}
|
||||
}
|
13
tree/generic/path.go
Normal file
13
tree/generic/path.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
)
|
||||
|
||||
func NewPathFromString(pathString string) path.Path {
|
||||
return path.NewPathFromString(NewElementNode, pathString)
|
||||
}
|
92
tree/generic/references.go
Normal file
92
tree/generic/references.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
"code.forgejo.org/f3/gof3/v3/util"
|
||||
)
|
||||
|
||||
type ErrorRemapReferencesRelative error
|
||||
|
||||
func RemapReferences(ctx context.Context, node NodeInterface, f f3.Interface) {
|
||||
for _, reference := range f.GetReferences() {
|
||||
toPath := path.NewPath()
|
||||
collectTo := func(ctx context.Context, parent, p path.Path, node NodeInterface) {
|
||||
element := NewNode()
|
||||
mappedID := node.GetMappedID()
|
||||
if mappedID.String() == "" {
|
||||
node.Trace("mapped ID for %s is not defined", p.ReadableString())
|
||||
}
|
||||
element.SetID(mappedID)
|
||||
toPath = toPath.Append(element.(path.PathElement))
|
||||
}
|
||||
from := reference.Get()
|
||||
isRelative := !strings.HasPrefix(from, "/")
|
||||
if isRelative && !strings.HasPrefix(from, "..") {
|
||||
panic(NewError[ErrorRemapReferencesRelative]("relative references that do not start with .. are not supported '%s'", from))
|
||||
}
|
||||
current := node.GetCurrentPath().String()
|
||||
fromPath := path.PathAbsolute(NewElementNode, current, from)
|
||||
node.GetTree().Apply(ctx, fromPath, NewApplyOptions(collectTo).SetWhere(ApplyEachNode))
|
||||
to := toPath.String()
|
||||
node.Trace("from '%s' to '%s'", fromPath.ReadableString(), toPath.ReadableString())
|
||||
if isRelative {
|
||||
currentMapped := node.GetParent().GetCurrentPath().PathMappedString().Join()
|
||||
// because the mapped ID of the current node has not been allocated yet
|
||||
// and it does not matter as long as it is replaced with ..
|
||||
// it will not work at all if a relative reference does not start with ..
|
||||
currentMapped += "/PLACEHODLER"
|
||||
to = path.PathRelativeString(currentMapped, to)
|
||||
}
|
||||
node.Trace("convert reference %s => %s", reference.Get(), to)
|
||||
reference.Set(to)
|
||||
}
|
||||
}
|
||||
|
||||
func NodeCollectReferences(ctx context.Context, node NodeInterface) []path.Path {
|
||||
pathToReferences := make(map[string]path.Path, 5)
|
||||
|
||||
tree := node.GetTree()
|
||||
|
||||
collect := func(ctx context.Context, parent path.Path, node NodeInterface) {
|
||||
util.MaybeTerminate(ctx)
|
||||
f := node.GetSelf().ToFormat()
|
||||
for _, reference := range f.GetReferences() {
|
||||
absoluteReference := path.PathAbsoluteString(node.GetCurrentPath().String(), reference.Get())
|
||||
if _, ok := pathToReferences[absoluteReference]; ok {
|
||||
continue
|
||||
}
|
||||
tree.ApplyAndGet(ctx, path.NewPathFromString(NewElementNode, absoluteReference), NewApplyOptions(func(ctx context.Context, parent, path path.Path, node NodeInterface) {
|
||||
pathToReferences[absoluteReference] = node.GetCurrentPath()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
node.Walk(ctx, path.NewPath(node.(path.PathElement)), NewWalkOptions(collect))
|
||||
|
||||
references := make([]path.Path, 0, len(pathToReferences))
|
||||
|
||||
for _, reference := range pathToReferences {
|
||||
tree.Debug("collect %s", reference)
|
||||
references = append(references, reference)
|
||||
}
|
||||
|
||||
return references
|
||||
}
|
||||
|
||||
func TreeCollectReferences(ctx context.Context, tree TreeInterface, p path.Path) []path.Path {
|
||||
var references []path.Path
|
||||
|
||||
tree.Apply(ctx, p, NewApplyOptions(func(ctx context.Context, parent, path path.Path, node NodeInterface) {
|
||||
references = NodeCollectReferences(ctx, node)
|
||||
}))
|
||||
|
||||
return references
|
||||
}
|
166
tree/generic/references_test.go
Normal file
166
tree/generic/references_test.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
// 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/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type referenceFormat struct {
|
||||
f3.Common
|
||||
R *f3.Reference
|
||||
}
|
||||
|
||||
func (o *referenceFormat) Clone() f3.Interface {
|
||||
clone := &referenceFormat{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
||||
|
||||
func (o *referenceFormat) GetReferences() f3.References {
|
||||
references := o.Common.GetReferences()
|
||||
if o.R != nil {
|
||||
references = append(references, o.R)
|
||||
}
|
||||
return references
|
||||
}
|
||||
|
||||
type referencesNodeDriver struct {
|
||||
NullDriver
|
||||
|
||||
f referenceFormat
|
||||
}
|
||||
|
||||
func (o *referencesNodeDriver) GetIDFromName(ctx context.Context, name string) id.NodeID {
|
||||
return id.NilID
|
||||
}
|
||||
|
||||
func (o *referencesNodeDriver) Get(context.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *referencesNodeDriver) NewFormat() f3.Interface {
|
||||
return &referenceFormat{}
|
||||
}
|
||||
|
||||
func (o *referencesNodeDriver) ToFormat() f3.Interface {
|
||||
return &o.f
|
||||
}
|
||||
|
||||
func (o *referencesNodeDriver) FromFormat(f f3.Interface) {
|
||||
o.f = *f.(*referenceFormat)
|
||||
}
|
||||
|
||||
func newReferencesNodeDriver() NodeDriverInterface {
|
||||
return &referencesNodeDriver{}
|
||||
}
|
||||
|
||||
type testTreeReferencesDriver struct {
|
||||
NullTreeDriver
|
||||
}
|
||||
|
||||
func newTestTreeReferencesDriver() TreeDriverInterface {
|
||||
return &testTreeReferencesDriver{}
|
||||
}
|
||||
|
||||
func (o *testTreeReferencesDriver) Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface {
|
||||
d := newReferencesNodeDriver()
|
||||
d.SetTreeDriver(o)
|
||||
return d
|
||||
}
|
||||
|
||||
var kindTestNodeReferences = kind.Kind("references")
|
||||
|
||||
type testNodeReferences struct {
|
||||
testNode
|
||||
}
|
||||
|
||||
func newTestTreeReferences() TreeInterface {
|
||||
tree := &testTree{}
|
||||
tree.Init(tree, newTestOptions())
|
||||
tree.SetDriver(newTestTreeReferencesDriver())
|
||||
tree.Register(kindTestNodeReferences, func(ctx context.Context, kind kind.Kind) NodeInterface {
|
||||
node := &testNodeReferences{}
|
||||
return node.Init(node)
|
||||
})
|
||||
return tree
|
||||
}
|
||||
|
||||
func TestTreeCollectReferences(t *testing.T) {
|
||||
tree := newTestTreeReferences()
|
||||
root := tree.Factory(context.Background(), kindTestNodeReferences)
|
||||
tree.SetRoot(root)
|
||||
|
||||
one := tree.Factory(context.Background(), kindTestNodeReferences)
|
||||
one.FromFormat(&referenceFormat{R: f3.NewReference("/somewhere")})
|
||||
one.SetID(id.NewNodeID("one"))
|
||||
root.SetChild(one)
|
||||
|
||||
// the second node that has the same reference is here to ensure
|
||||
// they are deduplicated
|
||||
two := tree.Factory(context.Background(), kindTestNodeReferences)
|
||||
two.FromFormat(&referenceFormat{R: f3.NewReference("/somewhere")})
|
||||
two.SetID(id.NewNodeID("two"))
|
||||
root.SetChild(two)
|
||||
|
||||
references := TreeCollectReferences(context.Background(), tree, path.NewPathFromString(NewElementNode, "/"))
|
||||
assert.Len(t, references, 1)
|
||||
assert.EqualValues(t, "/somewhere", references[0].String())
|
||||
}
|
||||
|
||||
func TestRemapReferences(t *testing.T) {
|
||||
tree := newTestTreeReferences()
|
||||
|
||||
root := tree.Factory(context.Background(), kindTestNodeReferences)
|
||||
tree.SetRoot(root)
|
||||
|
||||
one := tree.Factory(context.Background(), kindTestNodeReferences)
|
||||
one.SetID(id.NewNodeID("one"))
|
||||
one.SetMappedID(id.NewNodeID("remappedone"))
|
||||
one.SetParent(root)
|
||||
root.SetChild(one)
|
||||
|
||||
two := tree.Factory(context.Background(), kindTestNodeReferences)
|
||||
two.FromFormat(&referenceFormat{R: f3.NewReference("/one")})
|
||||
two.SetID(id.NewNodeID("two"))
|
||||
two.SetMappedID(id.NewNodeID("two"))
|
||||
two.SetParent(root)
|
||||
root.SetChild(two)
|
||||
|
||||
{
|
||||
f := two.ToFormat()
|
||||
RemapReferences(context.Background(), two, f)
|
||||
r := f.GetReferences()
|
||||
if assert.Len(t, r, 1) {
|
||||
assert.Equal(t, "/remappedone", r[0].Get())
|
||||
}
|
||||
}
|
||||
|
||||
three := tree.Factory(context.Background(), kindTestNodeReferences)
|
||||
three.FromFormat(&referenceFormat{R: f3.NewReference("../../one")})
|
||||
three.SetID(id.NewNodeID("three"))
|
||||
three.SetMappedID(id.NewNodeID("three"))
|
||||
three.SetParent(two)
|
||||
two.SetChild(three)
|
||||
|
||||
{
|
||||
f := three.ToFormat()
|
||||
RemapReferences(context.Background(), three, f)
|
||||
r := f.GetReferences()
|
||||
if assert.Len(t, r, 1) {
|
||||
assert.Equal(t, "../../remappedone", r[0].Get())
|
||||
}
|
||||
}
|
||||
|
||||
assert.Panics(t, func() { RemapReferences(context.Background(), three, &referenceFormat{R: f3.NewReference("./one")}) })
|
||||
}
|
134
tree/generic/tree.go
Normal file
134
tree/generic/tree.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
"code.forgejo.org/f3/gof3/v3/options"
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
)
|
||||
|
||||
type FactoryFun func(ctx context.Context, kind kind.Kind) NodeInterface
|
||||
|
||||
type kindMap map[kind.Kind]FactoryFun
|
||||
|
||||
type Tree struct {
|
||||
logger.Logger
|
||||
|
||||
opts options.Interface
|
||||
self TreeInterface
|
||||
driver TreeDriverInterface
|
||||
root NodeInterface
|
||||
kind kindMap
|
||||
}
|
||||
|
||||
func NewTree(opts options.Interface) TreeInterface {
|
||||
tree := &Tree{}
|
||||
return tree.Init(tree, opts)
|
||||
}
|
||||
|
||||
func (o *Tree) Init(self TreeInterface, opts options.Interface) TreeInterface {
|
||||
o.self = self
|
||||
o.kind = make(kindMap)
|
||||
o.SetDriver(NewNullTreeDriver())
|
||||
o.SetOptions(opts)
|
||||
o.SetLogger(opts.(options.LoggerInterface).GetLogger())
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *Tree) GetOptions() options.Interface { return o.opts }
|
||||
func (o *Tree) SetOptions(opts options.Interface) { o.opts = opts }
|
||||
|
||||
func (o *Tree) GetSelf() TreeInterface { return o.self }
|
||||
func (o *Tree) SetSelf(self TreeInterface) { o.self = self }
|
||||
|
||||
func (o *Tree) GetRoot() NodeInterface { return o.root }
|
||||
func (o *Tree) SetRoot(root NodeInterface) { o.root = root }
|
||||
|
||||
func (o *Tree) GetDriver() TreeDriverInterface { return o.driver }
|
||||
func (o *Tree) SetDriver(driver TreeDriverInterface) {
|
||||
driver.SetTree(o.GetSelf())
|
||||
o.driver = driver
|
||||
}
|
||||
|
||||
func (o *Tree) GetChildrenKind(parentKind kind.Kind) kind.Kind {
|
||||
return kind.KindNil
|
||||
}
|
||||
|
||||
func (o *Tree) GetPageSize() int { return o.GetDriver().GetPageSize() }
|
||||
|
||||
func (o *Tree) AllocateID() bool { return o.GetDriver().AllocateID() }
|
||||
|
||||
func (o *Tree) Walk(ctx context.Context, options *WalkOptions) {
|
||||
o.GetRoot().Walk(ctx, path.NewPath(), options)
|
||||
}
|
||||
|
||||
func (o *Tree) WalkAndGet(ctx context.Context, options *WalkOptions) {
|
||||
o.GetRoot().WalkAndGet(ctx, path.NewPath(), options)
|
||||
}
|
||||
|
||||
func (o *Tree) Clear(ctx context.Context) {
|
||||
rootKind := o.GetRoot().GetKind()
|
||||
o.SetRoot(o.Factory(ctx, rootKind))
|
||||
}
|
||||
|
||||
func (o *Tree) Exists(ctx context.Context, path path.Path) bool {
|
||||
return o.Find(path) != NilNode
|
||||
}
|
||||
|
||||
func (o *Tree) MustFind(path path.Path) NodeInterface {
|
||||
return o.GetRoot().MustFind(path.RemoveFirst())
|
||||
}
|
||||
|
||||
func (o *Tree) Find(path path.Path) NodeInterface {
|
||||
return o.GetRoot().Find(path.RemoveFirst())
|
||||
}
|
||||
|
||||
func (o *Tree) FindAndGet(ctx context.Context, path path.Path) NodeInterface {
|
||||
return o.GetRoot().FindAndGet(ctx, path.RemoveFirst())
|
||||
}
|
||||
|
||||
func (o *Tree) Diff(a, b NodeInterface) string {
|
||||
return o.GetDriver().Diff(a.GetDriver(), b.GetDriver())
|
||||
}
|
||||
|
||||
func (o *Tree) Apply(ctx context.Context, p path.Path, options *ApplyOptions) bool {
|
||||
if p.Empty() {
|
||||
return true
|
||||
}
|
||||
return o.GetRoot().Apply(ctx, path.NewPath(), p.RemoveFirst(), options)
|
||||
}
|
||||
|
||||
func (o *Tree) ApplyAndGet(ctx context.Context, path path.Path, options *ApplyOptions) bool {
|
||||
if path.Empty() {
|
||||
return true
|
||||
}
|
||||
return o.GetRoot().ApplyAndGet(ctx, path.RemoveFirst(), options)
|
||||
}
|
||||
|
||||
func (o *Tree) Factory(ctx context.Context, kind kind.Kind) NodeInterface {
|
||||
var node NodeInterface
|
||||
if factory, ok := o.kind[kind]; ok {
|
||||
node = factory(ctx, kind)
|
||||
} else {
|
||||
node = NewNode()
|
||||
}
|
||||
node.SetIsNil(false)
|
||||
node.SetKind(kind)
|
||||
node.SetTree(o.GetSelf())
|
||||
if o.GetDriver() != nil {
|
||||
nodeDriver := o.GetDriver().Factory(ctx, kind)
|
||||
nodeDriver.SetTreeDriver(o.GetDriver())
|
||||
node.SetDriver(nodeDriver)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (o *Tree) Register(kind kind.Kind, factory FactoryFun) {
|
||||
o.kind[kind] = factory
|
||||
}
|
90
tree/generic/tree_test.go
Normal file
90
tree/generic/tree_test.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
// 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"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTreeInit(t *testing.T) {
|
||||
tree := NewTree(newTestOptions())
|
||||
assert.NotNil(t, tree)
|
||||
|
||||
assert.True(t, tree == tree.GetSelf())
|
||||
tree.SetSelf(tree)
|
||||
assert.True(t, tree == tree.GetSelf())
|
||||
|
||||
other := NewTree(newTestOptions())
|
||||
assert.False(t, other == tree.GetSelf())
|
||||
assert.False(t, other.GetSelf() == tree)
|
||||
assert.False(t, other.GetSelf() == tree.GetSelf())
|
||||
}
|
||||
|
||||
func TestTreeRoot(t *testing.T) {
|
||||
tree := NewTree(newTestOptions())
|
||||
root := NewNode()
|
||||
tree.SetRoot(root)
|
||||
assert.True(t, root == tree.GetRoot())
|
||||
}
|
||||
|
||||
func TestTreePageSize(t *testing.T) {
|
||||
tree := NewTree(newTestOptions())
|
||||
assert.EqualValues(t, DefaultPageSize, tree.GetPageSize())
|
||||
pageSize := int(100)
|
||||
tree.GetDriver().SetPageSize(pageSize)
|
||||
assert.EqualValues(t, pageSize, tree.GetPageSize())
|
||||
}
|
||||
|
||||
func TestTreeAllocateID(t *testing.T) {
|
||||
tree := NewTree(newTestOptions())
|
||||
assert.True(t, tree.AllocateID())
|
||||
}
|
||||
|
||||
func TestTreeFactoryDerived(t *testing.T) {
|
||||
tree := newTestTree()
|
||||
root := tree.Factory(context.Background(), kindTestNodeLevelOne)
|
||||
tree.SetRoot(root)
|
||||
assert.True(t, root == tree.GetRoot())
|
||||
assert.True(t, kindTestNodeLevelOne == root.GetKind())
|
||||
assert.True(t, tree.GetSelf() == root.GetTree().GetSelf())
|
||||
|
||||
leveltwo := tree.Factory(context.Background(), kindTestNodeLevelTwo)
|
||||
leveltwo.SetParent(root)
|
||||
assert.True(t, root == leveltwo.GetParent())
|
||||
assert.True(t, kindTestNodeLevelTwo == leveltwo.GetKind())
|
||||
assert.True(t, tree.GetSelf() == leveltwo.GetTree().GetSelf())
|
||||
}
|
||||
|
||||
func TestTreeApply(t *testing.T) {
|
||||
tree := newTestTree()
|
||||
root := tree.Factory(context.Background(), kindTestNodeLevelOne)
|
||||
tree.SetRoot(root)
|
||||
|
||||
assert.True(t, tree.Apply(context.Background(), path.NewPath(), nil))
|
||||
assert.True(t, tree.Apply(context.Background(), path.NewPathFromString(NewElementNode, "/"),
|
||||
NewApplyOptions(func(ctx context.Context, parent, path path.Path, node NodeInterface) {
|
||||
require.Equal(t, root, node)
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
func TestTreeApplyAndGet(t *testing.T) {
|
||||
tree := newTestTree()
|
||||
root := tree.Factory(context.Background(), kindTestNodeLevelOne)
|
||||
tree.SetRoot(root)
|
||||
|
||||
assert.True(t, tree.ApplyAndGet(context.Background(), path.NewPath(), nil))
|
||||
assert.True(t, tree.ApplyAndGet(context.Background(), path.NewPathFromString(NewElementNode, "/"),
|
||||
NewApplyOptions(func(ctx context.Context, parent, path path.Path, node NodeInterface) {
|
||||
require.Equal(t, root, node)
|
||||
})),
|
||||
)
|
||||
}
|
320
tree/generic/unify.go
Normal file
320
tree/generic/unify.go
Normal file
|
@ -0,0 +1,320 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
"code.forgejo.org/f3/gof3/v3/util"
|
||||
)
|
||||
|
||||
type (
|
||||
UnifyUpsertFunc func(ctx context.Context, origin NodeInterface, originParent path.Path, destination NodeInterface, destinationParent path.Path)
|
||||
UnifyDeleteFunc func(ctx context.Context, destination NodeInterface, destinationParent path.Path)
|
||||
)
|
||||
|
||||
type UnifyOptions struct {
|
||||
destinationTree TreeInterface
|
||||
|
||||
upsert UnifyUpsertFunc
|
||||
delete UnifyDeleteFunc
|
||||
noremap bool
|
||||
}
|
||||
|
||||
func NewUnifyOptions(destinationTree TreeInterface) *UnifyOptions {
|
||||
return &UnifyOptions{
|
||||
destinationTree: destinationTree,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *UnifyOptions) SetUpsert(upsert UnifyUpsertFunc) *UnifyOptions {
|
||||
o.upsert = upsert
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *UnifyOptions) SetDelete(delete UnifyDeleteFunc) *UnifyOptions {
|
||||
o.delete = delete
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *UnifyOptions) SetNoRemap(noremap bool) *UnifyOptions {
|
||||
o.noremap = noremap
|
||||
return o
|
||||
}
|
||||
|
||||
func TreeUnifyPath(ctx context.Context, origin TreeInterface, p path.Path, destination TreeInterface, options *UnifyOptions) {
|
||||
if p.Empty() || p.First().(NodeInterface).GetID().String() == "." {
|
||||
return
|
||||
}
|
||||
|
||||
originRoot := origin.GetRoot()
|
||||
destinationRoot := destination.GetRoot()
|
||||
|
||||
if destinationRoot == nil {
|
||||
destinationRoot = destination.Factory(ctx, originRoot.GetKind())
|
||||
destination.SetRoot(destinationRoot)
|
||||
}
|
||||
|
||||
p = p.RemoveFirst()
|
||||
|
||||
if p.Empty() {
|
||||
return
|
||||
}
|
||||
|
||||
originNode := originRoot.GetChild(p.First().(NodeInterface).GetID()).GetSelf()
|
||||
NodeUnifyPath(ctx, originNode, path.NewPath(originRoot.(path.PathElement)), p.RemoveFirst(), path.NewPath(destinationRoot.(path.PathElement)), options)
|
||||
}
|
||||
|
||||
func TreeUnify(ctx context.Context, origin, destination TreeInterface, options *UnifyOptions) {
|
||||
origin.Trace("")
|
||||
|
||||
originRoot := origin.GetRoot()
|
||||
|
||||
if originRoot == nil {
|
||||
destination.SetRoot(nil)
|
||||
return
|
||||
}
|
||||
|
||||
destinationRoot := destination.GetRoot()
|
||||
|
||||
if destinationRoot == nil {
|
||||
destinationRoot = destination.Factory(ctx, originRoot.GetKind())
|
||||
destination.SetRoot(destinationRoot)
|
||||
NodeCopy(ctx, originRoot, destinationRoot, originRoot.GetID(), options)
|
||||
}
|
||||
|
||||
NodeUnify(ctx, originRoot, path.NewPath(), destinationRoot, path.NewPath(), options)
|
||||
}
|
||||
|
||||
func NodeCopy(ctx context.Context, origin, destination NodeInterface, destinationID id.NodeID, options *UnifyOptions) {
|
||||
f := origin.GetSelf().ToFormat()
|
||||
if options.noremap {
|
||||
origin.Trace("noremap")
|
||||
} else {
|
||||
RemapReferences(ctx, origin, f)
|
||||
}
|
||||
f.SetID(destinationID.String())
|
||||
destination.GetSelf().FromFormat(f)
|
||||
destination.Upsert(ctx)
|
||||
}
|
||||
|
||||
func NodeUnify(ctx context.Context, origin NodeInterface, originPath path.Path, destination NodeInterface, destinationPath path.Path, options *UnifyOptions) {
|
||||
origin.Trace("origin '%s' | destination '%s'", origin.GetCurrentPath().ReadableString(), destination.GetCurrentPath().ReadableString())
|
||||
util.MaybeTerminate(ctx)
|
||||
|
||||
originPath = originPath.Append(origin.(path.PathElement))
|
||||
destinationPath = destinationPath.Append(destination.(path.PathElement))
|
||||
|
||||
originChildren := origin.GetSelf().GetChildren()
|
||||
existing := make(map[id.NodeID]any, len(originChildren))
|
||||
for _, originChild := range originChildren {
|
||||
destinationID := GetMappedID(ctx, originChild, destination, options)
|
||||
destinationChild := destination.GetChild(destinationID)
|
||||
createDestinationChild := destinationChild == NilNode
|
||||
if createDestinationChild {
|
||||
destinationChild = options.destinationTree.Factory(ctx, originChild.GetKind())
|
||||
destinationChild.SetParent(destination)
|
||||
}
|
||||
|
||||
NodeCopy(ctx, originChild, destinationChild, destinationID, options)
|
||||
|
||||
if options.upsert != nil {
|
||||
options.upsert(ctx, originChild.GetSelf(), originPath, destinationChild.GetSelf(), destinationPath)
|
||||
}
|
||||
|
||||
if createDestinationChild {
|
||||
destination.SetChild(destinationChild)
|
||||
}
|
||||
SetMappedID(ctx, originChild, destinationChild, options)
|
||||
|
||||
existing[destinationChild.GetID()] = true
|
||||
|
||||
NodeUnify(ctx, originChild, originPath, destinationChild, destinationPath, options)
|
||||
}
|
||||
|
||||
destinationChildren := destination.GetSelf().GetChildren()
|
||||
for _, destinationChild := range destinationChildren {
|
||||
destinationID := destinationChild.GetID()
|
||||
if _, ok := existing[destinationID]; !ok {
|
||||
destinationChild.GetSelf().Delete(ctx)
|
||||
if options.delete != nil {
|
||||
options.delete(ctx, destinationChild.GetSelf(), destinationPath)
|
||||
}
|
||||
destination.DeleteChild(destinationID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SetMappedID(ctx context.Context, origin, destination NodeInterface, options *UnifyOptions) {
|
||||
if options.noremap {
|
||||
return
|
||||
}
|
||||
origin.SetMappedID(destination.GetID())
|
||||
}
|
||||
|
||||
func GetMappedID(ctx context.Context, origin, destinationParent NodeInterface, options *UnifyOptions) id.NodeID {
|
||||
if options.noremap {
|
||||
return origin.GetID()
|
||||
}
|
||||
|
||||
if i := origin.GetMappedID(); i != id.NilID {
|
||||
return i
|
||||
}
|
||||
|
||||
return destinationParent.LookupMappedID(origin.GetID())
|
||||
}
|
||||
|
||||
func NodeUnifyOne(ctx context.Context, origin NodeInterface, originPath, path, destinationPath path.Path, options *UnifyOptions) NodeInterface {
|
||||
destinationParent := destinationPath.Last().(NodeInterface)
|
||||
destinationID := GetMappedID(ctx, origin, destinationParent, options)
|
||||
origin.Trace("'%s' '%s' '%s' %v", originPath.ReadableString(), path.ReadableString(), destinationPath.ReadableString(), destinationID)
|
||||
destination := destinationParent.GetChild(destinationID)
|
||||
createDestination := destination == NilNode
|
||||
if createDestination {
|
||||
destination = options.destinationTree.Factory(ctx, origin.GetKind())
|
||||
destination.SetParent(destinationParent)
|
||||
}
|
||||
|
||||
NodeCopy(ctx, origin, destination, destinationID, options)
|
||||
|
||||
if options.upsert != nil {
|
||||
options.upsert(ctx, origin.GetSelf(), originPath, destination.GetSelf(), destinationPath)
|
||||
}
|
||||
|
||||
if createDestination {
|
||||
destinationParent.SetChild(destination)
|
||||
}
|
||||
origin.SetMappedID(destination.GetID())
|
||||
|
||||
return destination
|
||||
}
|
||||
|
||||
func NodeUnifyPath(ctx context.Context, origin NodeInterface, originPath, path, destinationPath path.Path, options *UnifyOptions) {
|
||||
origin.Trace("origin '%s' '%s' | destination '%s' | path '%s'", originPath.ReadableString(), origin.GetID(), destinationPath.ReadableString(), path.ReadableString())
|
||||
|
||||
util.MaybeTerminate(ctx)
|
||||
|
||||
if path.Empty() {
|
||||
NodeUnifyOne(ctx, origin, originPath, path, destinationPath, options)
|
||||
return
|
||||
}
|
||||
|
||||
id := path.First().(NodeInterface).GetID().String()
|
||||
|
||||
if id == "." {
|
||||
NodeUnifyOne(ctx, origin, originPath, path, destinationPath, options)
|
||||
return
|
||||
}
|
||||
|
||||
if id == ".." {
|
||||
parent, originPath := originPath.Pop()
|
||||
_, destinationPath := destinationPath.Pop()
|
||||
NodeUnifyPath(ctx, parent.(NodeInterface), originPath, path.RemoveFirst(), destinationPath, options)
|
||||
return
|
||||
}
|
||||
|
||||
destination := NodeUnifyOne(ctx, origin, originPath, path, destinationPath, options)
|
||||
|
||||
originPath = originPath.Append(origin.GetSelf())
|
||||
destinationPath = destinationPath.Append(destination.GetSelf())
|
||||
|
||||
child := origin.GetSelf().GetChild(path.First().(NodeInterface).GetID())
|
||||
if child == NilNode {
|
||||
panic(NewError[ErrorNodeNotFound]("%s has no child with id %s", originPath.String(), path.First().(NodeInterface).GetID()))
|
||||
}
|
||||
|
||||
NodeUnifyPath(ctx, child, originPath, path.RemoveFirst(), destinationPath, options)
|
||||
}
|
||||
|
||||
type ParallelApplyFunc func(ctx context.Context, origin, destination NodeInterface)
|
||||
|
||||
type ParallelApplyOptions struct {
|
||||
fun ParallelApplyFunc
|
||||
|
||||
where ApplyWhere
|
||||
noremap bool
|
||||
}
|
||||
|
||||
func NewParallelApplyOptions(fun ParallelApplyFunc) *ParallelApplyOptions {
|
||||
return &ParallelApplyOptions{
|
||||
fun: fun,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ParallelApplyOptions) SetWhere(where ApplyWhere) *ParallelApplyOptions {
|
||||
o.where = where
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *ParallelApplyOptions) SetNoRemap(noremap bool) *ParallelApplyOptions {
|
||||
o.noremap = noremap
|
||||
return o
|
||||
}
|
||||
|
||||
func TreePathRemap(ctx context.Context, origin TreeInterface, p path.Path, destination TreeInterface) path.Path {
|
||||
remappedPath := path.NewPath()
|
||||
remap := func(ctx context.Context, origin, destination NodeInterface) {
|
||||
remappedPath = destination.GetCurrentPath()
|
||||
}
|
||||
TreeParallelApply(ctx, origin, p, destination, NewParallelApplyOptions(remap))
|
||||
return remappedPath
|
||||
}
|
||||
|
||||
func TreeParallelApply(ctx context.Context, origin TreeInterface, path path.Path, destination TreeInterface, options *ParallelApplyOptions) bool {
|
||||
if path.Empty() {
|
||||
return true
|
||||
}
|
||||
return NodeParallelApply(ctx, origin.GetRoot(), path.RemoveFirst(), destination.GetRoot(), options)
|
||||
}
|
||||
|
||||
func NodeParallelApply(ctx context.Context, origin NodeInterface, path path.Path, destination NodeInterface, options *ParallelApplyOptions) bool {
|
||||
origin.Trace("origin '%s' | destination '%s' | path '%s'", origin.GetCurrentPath().ReadableString(), destination.GetCurrentPath().ReadableString(), path.ReadableString())
|
||||
|
||||
util.MaybeTerminate(ctx)
|
||||
|
||||
if path.Empty() {
|
||||
options.fun(ctx, origin, destination)
|
||||
return true
|
||||
}
|
||||
|
||||
i := path.First().(NodeInterface).GetID().String()
|
||||
|
||||
if i == "." {
|
||||
return NodeParallelApply(ctx, origin, path.RemoveFirst(), destination, options)
|
||||
}
|
||||
|
||||
if i == ".." {
|
||||
return NodeParallelApply(ctx, origin.GetParent(), path.RemoveFirst(), destination.GetParent(), options)
|
||||
}
|
||||
|
||||
if options.where == ApplyEachNode {
|
||||
options.fun(ctx, origin, destination)
|
||||
}
|
||||
|
||||
originChild := origin.GetSelf().GetChild(path.First().(NodeInterface).GetID())
|
||||
if originChild == NilNode {
|
||||
origin.Trace("no child %s", path.First().(NodeInterface).GetID())
|
||||
return false
|
||||
}
|
||||
var mappedID id.NodeID
|
||||
if options.noremap {
|
||||
mappedID = originChild.GetID()
|
||||
} else {
|
||||
mappedID = originChild.GetMappedID()
|
||||
if mappedID == id.NilID {
|
||||
origin.Trace("%s no mapped", originChild.GetID())
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
destinationChild := destination.GetChild(mappedID)
|
||||
if destinationChild == NilNode {
|
||||
panic(NewError[ErrorNodeNotFound]("%s has no child with id %s", destination.String(), originChild.GetMappedID()))
|
||||
}
|
||||
|
||||
return NodeParallelApply(ctx, originChild, path.RemoveFirst(), destinationChild, options)
|
||||
}
|
57
tree/generic/unify_test.go
Normal file
57
tree/generic/unify_test.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTreeUnify(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
originRoot bool
|
||||
destinationRoot bool
|
||||
}{
|
||||
{
|
||||
name: "Origin",
|
||||
originRoot: true,
|
||||
destinationRoot: false,
|
||||
},
|
||||
{
|
||||
name: "Destination",
|
||||
originRoot: false,
|
||||
destinationRoot: true,
|
||||
},
|
||||
{
|
||||
name: "None",
|
||||
originRoot: false,
|
||||
destinationRoot: false,
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
origin := newTestTree()
|
||||
if testCase.originRoot {
|
||||
origin.SetRoot(origin.Factory(ctx, kindTestNodeLevelOne))
|
||||
}
|
||||
destination := newTestTree()
|
||||
if testCase.destinationRoot {
|
||||
destination.SetRoot(destination.Factory(ctx, kindTestNodeLevelTwo))
|
||||
}
|
||||
TreeUnify(ctx, origin, destination, NewUnifyOptions(destination))
|
||||
if testCase.originRoot {
|
||||
destinationRoot := destination.GetRoot()
|
||||
assert.NotNil(t, destinationRoot)
|
||||
assert.EqualValues(t, kindTestNodeLevelOne, destinationRoot.GetKind())
|
||||
} else {
|
||||
assert.EqualValues(t, nil, destination.GetRoot())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
63
tree/generic/walk_options.go
Normal file
63
tree/generic/walk_options.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
)
|
||||
|
||||
type WalkFunc func(context.Context, path.Path, NodeInterface)
|
||||
|
||||
type WalkOptions struct {
|
||||
fun WalkFunc
|
||||
}
|
||||
|
||||
func NewWalkOptions(fun WalkFunc) *WalkOptions {
|
||||
return &WalkOptions{
|
||||
fun: fun,
|
||||
}
|
||||
}
|
||||
|
||||
type ApplyFunc func(ctx context.Context, parentPath, path path.Path, node NodeInterface)
|
||||
|
||||
type ApplyWhere bool
|
||||
|
||||
const (
|
||||
ApplyEachNode ApplyWhere = true
|
||||
ApplyLastNode ApplyWhere = false
|
||||
)
|
||||
|
||||
type ApplySearch bool
|
||||
|
||||
const (
|
||||
ApplySearchByName ApplySearch = true
|
||||
ApplySearchByID ApplySearch = false
|
||||
)
|
||||
|
||||
type ApplyOptions struct {
|
||||
fun ApplyFunc
|
||||
where ApplyWhere
|
||||
search ApplySearch
|
||||
}
|
||||
|
||||
func NewApplyOptions(fun ApplyFunc) *ApplyOptions {
|
||||
return &ApplyOptions{
|
||||
fun: fun,
|
||||
where: ApplyLastNode,
|
||||
search: ApplySearchByID,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ApplyOptions) SetWhere(where ApplyWhere) *ApplyOptions {
|
||||
o.where = where
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *ApplyOptions) SetSearch(search ApplySearch) *ApplyOptions {
|
||||
o.search = search
|
||||
return o
|
||||
}
|
350
tree/memory/memory.go
Normal file
350
tree/memory/memory.go
Normal file
|
@ -0,0 +1,350 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
"code.forgejo.org/f3/gof3/v3/options"
|
||||
options_logger "code.forgejo.org/f3/gof3/v3/options/logger"
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
)
|
||||
|
||||
type IDAllocatorInterface interface {
|
||||
allocateID(id string) string
|
||||
isNull() bool
|
||||
}
|
||||
|
||||
type idAllocatorGenerator struct {
|
||||
prefix string
|
||||
lastID rune
|
||||
}
|
||||
|
||||
func NewIDAllocatorGenerator(prefix string) IDAllocatorInterface {
|
||||
return &idAllocatorGenerator{
|
||||
prefix: prefix,
|
||||
lastID: 'A',
|
||||
}
|
||||
}
|
||||
|
||||
func (o *idAllocatorGenerator) allocateID(string) string {
|
||||
r := fmt.Sprintf("%s-%c", o.prefix, o.lastID)
|
||||
o.lastID++
|
||||
return r
|
||||
}
|
||||
|
||||
func (o *idAllocatorGenerator) isNull() bool { return false }
|
||||
|
||||
type idAllocatorNull struct{}
|
||||
|
||||
func (o *idAllocatorNull) allocateID(id string) string { return id }
|
||||
|
||||
func (o *idAllocatorNull) isNull() bool { return true }
|
||||
|
||||
func NewIDAllocatorNull() IDAllocatorInterface {
|
||||
return &idAllocatorNull{}
|
||||
}
|
||||
|
||||
type memoryOptions struct {
|
||||
options.Options
|
||||
options_logger.OptionsLogger
|
||||
|
||||
IDAllocator IDAllocatorInterface
|
||||
}
|
||||
|
||||
func NewOptions(idAllocator IDAllocatorInterface) options.Interface {
|
||||
opts := &memoryOptions{}
|
||||
opts.IDAllocator = idAllocator
|
||||
opts.SetName("memory")
|
||||
l := logger.NewLogger()
|
||||
l.SetLevel(logger.Trace)
|
||||
opts.SetLogger(l)
|
||||
return opts
|
||||
}
|
||||
|
||||
type memoryStorage struct {
|
||||
idAllocator IDAllocatorInterface
|
||||
root *memoryStorageNode
|
||||
}
|
||||
|
||||
func newmemoryStorage(opts options.Interface) *memoryStorage {
|
||||
return &memoryStorage{
|
||||
idAllocator: opts.(*memoryOptions).IDAllocator,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *memoryStorage) newStorageNode(id string) *memoryStorageNode {
|
||||
return &memoryStorageNode{
|
||||
storage: o,
|
||||
f: NewFormat(o.idAllocator.allocateID(id)),
|
||||
children: make(map[string]*memoryStorageNode),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *memoryStorage) Find(path path.PathString) *memoryStorageNode {
|
||||
p := path.Elements()
|
||||
fmt.Printf("memoryStorage Find %s\n", path.Join())
|
||||
current := o.root
|
||||
for {
|
||||
fmt.Printf(" memoryStorage Find lookup '%s'\n", current.f.GetID())
|
||||
if current.f.GetID() != p[0] {
|
||||
panic("")
|
||||
}
|
||||
p = p[1:]
|
||||
if len(p) == 0 {
|
||||
return current
|
||||
}
|
||||
if next, ok := current.children[p[0]]; ok {
|
||||
current = next
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type memoryStorageNode struct {
|
||||
storage *memoryStorage
|
||||
f *FormatMemory
|
||||
children map[string]*memoryStorageNode
|
||||
}
|
||||
|
||||
type treeDriver struct {
|
||||
generic.NullTreeDriver
|
||||
storage *memoryStorage
|
||||
allocateID bool
|
||||
}
|
||||
|
||||
func newTreeDriver(opts options.Interface) *treeDriver {
|
||||
tree := &treeDriver{
|
||||
storage: newmemoryStorage(opts),
|
||||
allocateID: !opts.(*memoryOptions).IDAllocator.isNull(),
|
||||
}
|
||||
tree.Init()
|
||||
return tree
|
||||
}
|
||||
|
||||
func (o *treeDriver) AllocateID() bool { return o.allocateID }
|
||||
|
||||
func (o *treeDriver) Factory(ctx context.Context, kind kind.Kind) generic.NodeDriverInterface {
|
||||
return &Driver{}
|
||||
}
|
||||
|
||||
type Driver struct {
|
||||
generic.NullDriver
|
||||
f *FormatMemory
|
||||
}
|
||||
|
||||
type FormatMemory struct {
|
||||
f3.Common
|
||||
Ref *f3.Reference
|
||||
Content string
|
||||
}
|
||||
|
||||
func NewFormat(id string) *FormatMemory {
|
||||
f := &FormatMemory{}
|
||||
f.Ref = f3.NewReference("")
|
||||
f.Content = "????"
|
||||
f.SetID(id)
|
||||
return f
|
||||
}
|
||||
|
||||
func (o *FormatMemory) GetReferences() f3.References {
|
||||
references := o.Common.GetReferences()
|
||||
if o.Ref.Get() != "" {
|
||||
references = append(references, o.Ref)
|
||||
}
|
||||
return references
|
||||
}
|
||||
|
||||
func (o *Driver) NewFormat() f3.Interface {
|
||||
return &FormatMemory{}
|
||||
}
|
||||
|
||||
func (o *Driver) ToFormat() f3.Interface {
|
||||
if o == nil || o.f == nil {
|
||||
return o.NewFormat()
|
||||
}
|
||||
return &FormatMemory{
|
||||
Common: o.f.Common,
|
||||
Ref: o.f.Ref,
|
||||
Content: o.f.Content,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Driver) FromFormat(f f3.Interface) {
|
||||
m := f.(*FormatMemory)
|
||||
o.f = &FormatMemory{
|
||||
Common: m.Common,
|
||||
Ref: m.Ref,
|
||||
Content: m.Content,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Driver) Equals(ctx context.Context, other generic.NodeInterface) bool {
|
||||
switch i := other.GetDriver().(type) {
|
||||
case *Driver:
|
||||
return o.f.Content == i.f.Content
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Driver) String() string {
|
||||
if o.f == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return o.f.Content
|
||||
}
|
||||
|
||||
func (o *Driver) Get(ctx context.Context) bool {
|
||||
node := o.GetNode()
|
||||
o.Trace("id '%s' '%s'", node.GetID(), node.GetCurrentPath().ReadableString())
|
||||
storage := o.getStorage(node, node.GetCurrentPath())
|
||||
if storage != nil {
|
||||
o.f = storage.f
|
||||
}
|
||||
node.List(ctx)
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *Driver) Put(ctx context.Context) id.NodeID {
|
||||
return o.upsert(ctx)
|
||||
}
|
||||
|
||||
func (o *Driver) Patch(ctx context.Context) {
|
||||
o.upsert(ctx)
|
||||
}
|
||||
|
||||
func (o *Driver) upsert(ctx context.Context) id.NodeID {
|
||||
node := o.GetNode()
|
||||
path := node.GetParent().GetCurrentPath()
|
||||
storageParent := o.getStorage(node, path)
|
||||
i := node.GetID()
|
||||
o.Trace("node id '%s'", i)
|
||||
if existing, ok := storageParent.children[i.String()]; ok {
|
||||
o.Trace("update %s content=%s ref=%s", node.GetCurrentPath().ReadableString(), o.f.Content, o.f.Ref)
|
||||
existing.f = o.f
|
||||
} else {
|
||||
storageChild := storageParent.storage.newStorageNode(i.String())
|
||||
storageID := storageChild.f.GetID()
|
||||
storageParent.children[storageID] = storageChild
|
||||
i = id.NewNodeID(storageID)
|
||||
if o.f == nil {
|
||||
o.f = storageChild.f
|
||||
} else {
|
||||
o.f.SetID(storageChild.f.GetID())
|
||||
}
|
||||
o.Trace("create '%s' '%s'", node.GetCurrentPath().ReadableString(), i)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (o *Driver) Delete(ctx context.Context) {
|
||||
node := o.GetNode()
|
||||
storage := o.getStorage(node, node.GetParent().GetCurrentPath())
|
||||
if storage != nil && storage.children != nil {
|
||||
id := node.GetID().String()
|
||||
delete(storage.children, id)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Driver) getStorage(node generic.NodeInterface, path path.Path) *memoryStorageNode {
|
||||
storage := node.GetTree().GetDriver().(*treeDriver).storage
|
||||
return storage.Find(path.PathString())
|
||||
}
|
||||
|
||||
func (o *Driver) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
|
||||
node := o.GetNode()
|
||||
o.Trace("'%s'", node.GetCurrentPath().ReadableString())
|
||||
storage := o.getStorage(node, node.GetCurrentPath())
|
||||
|
||||
children := generic.NewChildrenSlice(0)
|
||||
if storage != nil {
|
||||
for i := range storage.children {
|
||||
node := node.CreateChild(context.Background())
|
||||
childID := id.NewNodeID(i)
|
||||
node.SetID(childID)
|
||||
children = append(children, node)
|
||||
}
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
func (o *Driver) GetIDFromName(ctx context.Context, content string) id.NodeID {
|
||||
node := o.GetNode()
|
||||
o.Trace("'%s'", node.GetCurrentPath().ReadableString())
|
||||
storage := o.getStorage(node, node.GetCurrentPath())
|
||||
|
||||
if storage != nil {
|
||||
for i, child := range storage.children {
|
||||
if child.f.Content == content {
|
||||
o.Trace("found '%s'", i)
|
||||
return id.NewNodeID(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
return id.NilID
|
||||
}
|
||||
|
||||
type treeMemory struct {
|
||||
generic.Tree
|
||||
}
|
||||
|
||||
func newTreeMemory(ctx context.Context, opts options.Interface) generic.TreeInterface {
|
||||
t := &treeMemory{}
|
||||
t.Init(t, opts)
|
||||
t.GetLogger().SetLevel(logger.Trace)
|
||||
t.Register(kind.KindNil, func(ctx context.Context, kind kind.Kind) generic.NodeInterface {
|
||||
return generic.NewNode()
|
||||
})
|
||||
|
||||
treeDriver := newTreeDriver(opts)
|
||||
t.SetDriver(treeDriver)
|
||||
|
||||
f := NewFormat("")
|
||||
f.Content = "ROOT"
|
||||
|
||||
storage := treeDriver.storage
|
||||
storage.root = &memoryStorageNode{
|
||||
storage: storage,
|
||||
f: f,
|
||||
children: make(map[string]*memoryStorageNode),
|
||||
}
|
||||
root := t.Factory(ctx, kind.KindRoot)
|
||||
root.FromFormat(f)
|
||||
t.SetRoot(root)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func SetContent(node generic.NodeInterface, content string) {
|
||||
f := node.GetDriver().ToFormat().(*FormatMemory)
|
||||
f.Content = content
|
||||
node.FromFormat(f)
|
||||
}
|
||||
|
||||
func GetContent(node generic.NodeInterface) string {
|
||||
return node.GetDriver().ToFormat().(*FormatMemory).Content
|
||||
}
|
||||
|
||||
func SetRef(node generic.NodeInterface, ref string) {
|
||||
f := node.GetDriver().ToFormat().(*FormatMemory)
|
||||
f.Ref = f3.NewReference(ref)
|
||||
node.FromFormat(f)
|
||||
}
|
||||
|
||||
func GetRef(node generic.NodeInterface) string {
|
||||
return node.GetDriver().ToFormat().(*FormatMemory).Ref.Get()
|
||||
}
|
||||
|
||||
func init() {
|
||||
generic.RegisterFactory("memory", newTreeMemory)
|
||||
}
|
397
tree/tests/f3/creator.go
Normal file
397
tree/tests/f3/creator.go
Normal 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
260
tree/tests/f3/f3_test.go
Normal 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)
|
||||
}
|
||||
}
|
105
tree/tests/f3/filesystem_test.go
Normal file
105
tree/tests/f3/filesystem_test.go
Normal 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
224
tree/tests/f3/fixture.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
30
tree/tests/f3/forge/base.go
Normal file
30
tree/tests/f3/forge/base.go
Normal 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"
|
||||
}
|
35
tree/tests/f3/forge/factory.go
Normal file
35
tree/tests/f3/forge/factory.go
Normal 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
|
||||
}
|
26
tree/tests/f3/forge/interface.go
Normal file
26
tree/tests/f3/forge/interface.go
Normal 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
|
||||
}
|
254
tree/tests/f3/forge_compliance.go
Normal file
254
tree/tests/f3/forge_compliance.go
Normal 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)
|
||||
}
|
19
tree/tests/f3/forge_test.go
Normal file
19
tree/tests/f3/forge_test.go
Normal 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
474
tree/tests/f3/generator.go
Normal 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}
|
||||
}
|
125
tree/tests/f3/helpers_repository_test.go
Normal file
125
tree/tests/f3/helpers_repository_test.go
Normal 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
19
tree/tests/f3/init.go
Normal 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"
|
||||
)
|
16
tree/tests/f3/interface.go
Normal file
16
tree/tests/f3/interface.go
Normal 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()
|
||||
}
|
9
tree/tests/f3/main_test.go
Normal file
9
tree/tests/f3/main_test.go
Normal 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"
|
||||
)
|
71
tree/tests/f3/pullrequest_test.go
Normal file
71
tree/tests/f3/pullrequest_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
74
tree/tests/generic/compare_test.go
Normal file
74
tree/tests/generic/compare_test.go
Normal 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("")))
|
||||
}
|
||||
}
|
193
tree/tests/generic/memory_test.go
Normal file
193
tree/tests/generic/memory_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
98
tree/tests/generic/mirror_test.go
Normal file
98
tree/tests/generic/mirror_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
341
tree/tests/generic/node_walk_test.go
Normal file
341
tree/tests/generic/node_walk_test.go
Normal 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))
|
||||
}
|
74
tree/tests/generic/references_test.go
Normal file
74
tree/tests/generic/references_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
626
tree/tests/generic/unify_test.go
Normal file
626
tree/tests/generic/unify_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue