Skip to content

Commit 31f7842

Browse files
authored
Merge branch 'master' into dependabot/github_actions/actions-d86afc3ec3
2 parents 944242d + 4b0c341 commit 31f7842

File tree

7 files changed

+432
-0
lines changed

7 files changed

+432
-0
lines changed

examples/gno.land/p/n2p5/loci/gno.mod

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module gno.land/p/n2p5/loci
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// loci is a single purpose datastore keyed by the caller's address. It has two
2+
// functions: Set and Get. loci is plural for locus, which is a central or core
3+
// place where something is found or from which it originates. In this case,
4+
// it's a simple key-value store where an address (the key) can store exactly
5+
// one value (in the form of a byte slice). Only the caller can set the value
6+
// for their address, but anyone can retrieve the value for any address.
7+
package loci
8+
9+
import (
10+
"std"
11+
12+
"gno.land/p/demo/avl"
13+
)
14+
15+
// LociStore is a simple key-value store that uses
16+
// an AVL tree to store the data.
17+
type LociStore struct {
18+
internal *avl.Tree
19+
}
20+
21+
// New creates a reference to a new LociStore.
22+
func New() *LociStore {
23+
return &LociStore{
24+
internal: avl.NewTree(),
25+
}
26+
}
27+
28+
// Set stores a byte slice in the AVL tree using the `std.PrevRealm().Addr()`
29+
// string as the key.
30+
func (s *LociStore) Set(value []byte) {
31+
key := string(std.PrevRealm().Addr())
32+
s.internal.Set(key, value)
33+
}
34+
35+
// Get retrieves a byte slice from the AVL tree using the provided address.
36+
// The return values are the byte slice value and a boolean indicating
37+
// whether the value exists.
38+
func (s *LociStore) Get(addr std.Address) []byte {
39+
value, exists := s.internal.Get(string(addr))
40+
if !exists {
41+
return nil
42+
}
43+
return value.([]byte)
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package loci
2+
3+
import (
4+
"std"
5+
"testing"
6+
7+
"gno.land/p/demo/testutils"
8+
)
9+
10+
func TestLociStore(t *testing.T) {
11+
t.Parallel()
12+
13+
u1 := testutils.TestAddress("u1")
14+
u2 := testutils.TestAddress("u1")
15+
16+
t.Run("TestSet", func(t *testing.T) {
17+
t.Parallel()
18+
store := New()
19+
u1 := testutils.TestAddress("u1")
20+
21+
m1 := []byte("hello")
22+
m2 := []byte("world")
23+
std.TestSetOrigCaller(u1)
24+
25+
// Ensure that the value is nil before setting it.
26+
r1 := store.Get(u1)
27+
if r1 != nil {
28+
t.Errorf("expected value to be nil, got '%s'", r1)
29+
}
30+
store.Set(m1)
31+
// Ensure that the value is correct after setting it.
32+
r2 := store.Get(u1)
33+
if string(r2) != "hello" {
34+
t.Errorf("expected value to be 'hello', got '%s'", r2)
35+
}
36+
store.Set(m2)
37+
// Ensure that the value is correct after overwriting it.
38+
r3 := store.Get(u1)
39+
if string(r3) != "world" {
40+
t.Errorf("expected value to be 'world', got '%s'", r3)
41+
}
42+
})
43+
t.Run("TestGet", func(t *testing.T) {
44+
t.Parallel()
45+
store := New()
46+
u1 := testutils.TestAddress("u1")
47+
u2 := testutils.TestAddress("u2")
48+
u3 := testutils.TestAddress("u3")
49+
u4 := testutils.TestAddress("u4")
50+
51+
m1 := []byte("hello")
52+
m2 := []byte("world")
53+
m3 := []byte("goodbye")
54+
55+
std.TestSetOrigCaller(u1)
56+
store.Set(m1)
57+
std.TestSetOrigCaller(u2)
58+
store.Set(m2)
59+
std.TestSetOrigCaller(u3)
60+
store.Set(m3)
61+
62+
// Ensure that the value is correct after setting it.
63+
r0 := store.Get(u4)
64+
if r0 != nil {
65+
t.Errorf("expected value to be nil, got '%s'", r0)
66+
}
67+
// Ensure that the value is correct after setting it.
68+
r1 := store.Get(u1)
69+
if string(r1) != "hello" {
70+
t.Errorf("expected value to be 'hello', got '%s'", r1)
71+
}
72+
// Ensure that the value is correct after setting it.
73+
r2 := store.Get(u2)
74+
if string(r2) != "world" {
75+
t.Errorf("expected value to be 'world', got '%s'", r2)
76+
}
77+
// Ensure that the value is correct after setting it.
78+
r3 := store.Get(u3)
79+
if string(r3) != "goodbye" {
80+
t.Errorf("expected value to be 'goodbye', got '%s'", r3)
81+
}
82+
})
83+
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package hof
2+
3+
import (
4+
"errors"
5+
6+
"gno.land/p/demo/avl"
7+
"gno.land/p/demo/ufmt"
8+
"gno.land/p/jeronimoalbi/datasource"
9+
)
10+
11+
func NewDatasource() Datasource {
12+
return Datasource{exhibition}
13+
}
14+
15+
type Datasource struct {
16+
exhibition *Exhibition
17+
}
18+
19+
func (ds Datasource) Size() int { return ds.exhibition.itemsSorted.Size() }
20+
21+
func (ds Datasource) Records(q datasource.Query) datasource.Iterator {
22+
return &iterator{
23+
exhibition: ds.exhibition,
24+
index: q.Offset,
25+
maxIndex: q.Offset + q.Count,
26+
}
27+
}
28+
29+
func (ds Datasource) Record(id string) (datasource.Record, error) {
30+
v, found := ds.exhibition.itemsSorted.Get(id)
31+
if !found {
32+
return nil, errors.New("realm submission not found")
33+
}
34+
return record{v.(*Item)}, nil
35+
}
36+
37+
type record struct {
38+
item *Item
39+
}
40+
41+
func (r record) ID() string { return r.item.id.String() }
42+
func (r record) String() string { return r.item.pkgpath }
43+
44+
func (r record) Fields() (datasource.Fields, error) {
45+
fields := avl.NewTree()
46+
fields.Set(
47+
"details",
48+
ufmt.Sprintf("Votes: ⏶ %d - ⏷ %d", r.item.upvote.Size(), r.item.downvote.Size()),
49+
)
50+
return fields, nil
51+
}
52+
53+
func (r record) Content() (string, error) {
54+
content := ufmt.Sprintf("# Submission #%d\n\n", int(r.item.id))
55+
content += r.item.Render(false)
56+
return content, nil
57+
}
58+
59+
type iterator struct {
60+
exhibition *Exhibition
61+
index, maxIndex int
62+
record *record
63+
}
64+
65+
func (it iterator) Record() datasource.Record { return it.record }
66+
func (it iterator) Err() error { return nil }
67+
68+
func (it *iterator) Next() bool {
69+
if it.index >= it.maxIndex || it.index >= it.exhibition.itemsSorted.Size() {
70+
return false
71+
}
72+
73+
_, v := it.exhibition.itemsSorted.GetByIndex(it.index)
74+
it.record = &record{v.(*Item)}
75+
it.index++
76+
return true
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package hof
2+
3+
import (
4+
"testing"
5+
6+
"gno.land/p/demo/avl"
7+
"gno.land/p/demo/uassert"
8+
"gno.land/p/demo/urequire"
9+
"gno.land/p/jeronimoalbi/datasource"
10+
)
11+
12+
var (
13+
_ datasource.Datasource = (*Datasource)(nil)
14+
_ datasource.Record = (*record)(nil)
15+
_ datasource.ContentRecord = (*record)(nil)
16+
_ datasource.Iterator = (*iterator)(nil)
17+
)
18+
19+
func TestDatasourceRecords(t *testing.T) {
20+
cases := []struct {
21+
name string
22+
items []*Item
23+
recordIDs []string
24+
options []datasource.QueryOption
25+
}{
26+
{
27+
name: "all items",
28+
items: []*Item{{id: 1}, {id: 2}, {id: 3}},
29+
recordIDs: []string{"0000001", "0000002", "0000003"},
30+
},
31+
{
32+
name: "with offset",
33+
items: []*Item{{id: 1}, {id: 2}, {id: 3}},
34+
recordIDs: []string{"0000002", "0000003"},
35+
options: []datasource.QueryOption{datasource.WithOffset(1)},
36+
},
37+
{
38+
name: "with count",
39+
items: []*Item{{id: 1}, {id: 2}, {id: 3}},
40+
recordIDs: []string{"0000001", "0000002"},
41+
options: []datasource.QueryOption{datasource.WithCount(2)},
42+
},
43+
{
44+
name: "with offset and count",
45+
items: []*Item{{id: 1}, {id: 2}, {id: 3}},
46+
recordIDs: []string{"0000002"},
47+
options: []datasource.QueryOption{
48+
datasource.WithOffset(1),
49+
datasource.WithCount(1),
50+
},
51+
},
52+
}
53+
54+
for _, tc := range cases {
55+
t.Run(tc.name, func(t *testing.T) {
56+
// Initialize a local instance of exhibition
57+
exhibition := &Exhibition{itemsSorted: avl.NewTree()}
58+
for _, item := range tc.items {
59+
exhibition.itemsSorted.Set(item.id.String(), item)
60+
}
61+
62+
// Get a records iterator
63+
ds := Datasource{exhibition}
64+
query := datasource.NewQuery(tc.options...)
65+
iter := ds.Records(query)
66+
67+
// Start asserting
68+
urequire.Equal(t, len(tc.items), ds.Size(), "datasource size")
69+
70+
var records []datasource.Record
71+
for iter.Next() {
72+
records = append(records, iter.Record())
73+
}
74+
urequire.Equal(t, len(tc.recordIDs), len(records), "record count")
75+
76+
for i, r := range records {
77+
uassert.Equal(t, tc.recordIDs[i], r.ID())
78+
}
79+
})
80+
}
81+
}
82+
83+
func TestDatasourceRecord(t *testing.T) {
84+
cases := []struct {
85+
name string
86+
items []*Item
87+
id string
88+
err string
89+
}{
90+
{
91+
name: "found",
92+
items: []*Item{{id: 1}, {id: 2}, {id: 3}},
93+
id: "0000001",
94+
},
95+
{
96+
name: "no found",
97+
items: []*Item{{id: 1}, {id: 2}, {id: 3}},
98+
id: "42",
99+
err: "realm submission not found",
100+
},
101+
}
102+
103+
for _, tc := range cases {
104+
t.Run(tc.name, func(t *testing.T) {
105+
// Initialize a local instance of exhibition
106+
exhibition := &Exhibition{itemsSorted: avl.NewTree()}
107+
for _, item := range tc.items {
108+
exhibition.itemsSorted.Set(item.id.String(), item)
109+
}
110+
111+
// Get a single record
112+
ds := Datasource{exhibition}
113+
r, err := ds.Record(tc.id)
114+
115+
// Start asserting
116+
if tc.err != "" {
117+
uassert.ErrorContains(t, err, tc.err)
118+
return
119+
}
120+
121+
urequire.NoError(t, err, "no error")
122+
urequire.NotEqual(t, nil, r, "record not nil")
123+
uassert.Equal(t, tc.id, r.ID())
124+
})
125+
}
126+
}
127+
128+
func TestItemRecord(t *testing.T) {
129+
pkgpath := "gno.land/r/demo/test"
130+
item := Item{
131+
id: 1,
132+
pkgpath: pkgpath,
133+
blockNum: 42,
134+
upvote: avl.NewTree(),
135+
downvote: avl.NewTree(),
136+
}
137+
item.downvote.Set("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", struct{}{})
138+
item.upvote.Set("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", struct{}{})
139+
item.upvote.Set("g1w4ek2u3jta047h6lta047h6lta047h6l9huexc", struct{}{})
140+
141+
r := record{&item}
142+
143+
uassert.Equal(t, "0000001", r.ID())
144+
uassert.Equal(t, pkgpath, r.String())
145+
146+
fields, _ := r.Fields()
147+
details, found := fields.Get("details")
148+
urequire.True(t, found, "details field")
149+
uassert.Equal(t, "Votes: ⏶ 2 - ⏷ 1", details)
150+
151+
content, _ := r.Content()
152+
wantContent := "# Submission #1\n\n\n```\ngno.land/r/demo/test\n```\n\nby demo\n\n" +
153+
"[View realm](/r/demo/test)\n\nSubmitted at Block #42\n\n" +
154+
"#### [2👍](/r/leon/hof$help&func=Upvote&pkgpath=gno.land/r/demo/test) - " +
155+
"[1👎](/r/leon/hof$help&func=Downvote&pkgpath=gno.land/r/demo/test)\n\n"
156+
uassert.Equal(t, wantContent, content)
157+
}

examples/gno.land/r/n2p5/loci/gno.mod

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module gno.land/r/n2p5/loci

0 commit comments

Comments
 (0)