1
- use anyhow:: Context ;
1
+ use anyhow:: Context as _ ;
2
2
use spin_factor_key_value:: {
3
- runtime_config:: spin:: { MakeKeyValueStore , RuntimeConfigResolver , StoreConfig } ,
3
+ runtime_config:: spin:: { MakeKeyValueStore , RuntimeConfigResolver } ,
4
4
KeyValueFactor , RuntimeConfig ,
5
5
} ;
6
6
use spin_factor_key_value_redis:: RedisKeyValueStore ;
7
- use spin_factor_key_value_spin:: { SpinKeyValueRuntimeConfig , SpinKeyValueStore } ;
7
+ use spin_factor_key_value_spin:: SpinKeyValueStore ;
8
8
use spin_factors:: { FactorRuntimeConfigSource , RuntimeConfigSourceFinalizer , RuntimeFactors } ;
9
9
use spin_factors_test:: { toml, TestEnvironment } ;
10
+ use spin_world:: v2:: key_value:: HostStore ;
10
11
use std:: { collections:: HashSet , sync:: Arc } ;
11
12
12
13
#[ derive( RuntimeFactors ) ]
13
14
struct TestFactors {
14
15
key_value : KeyValueFactor ,
15
16
}
16
17
17
- fn default_key_value_resolver ( ) -> anyhow:: Result < ( RuntimeConfigResolver , tempdir:: TempDir ) > {
18
- let mut test_resolver = RuntimeConfigResolver :: new ( ) ;
19
- test_resolver. register_store_type ( SpinKeyValueStore :: new (
20
- std:: env:: current_dir ( ) . context ( "failed to get current directory" ) ?,
21
- ) ) ?;
22
- let tmp_dir = tempdir:: TempDir :: new ( "example" ) ?;
23
- let path = tmp_dir. path ( ) . to_path_buf ( ) ;
24
- let default_config = SpinKeyValueRuntimeConfig :: default ( Some ( path) ) ;
25
- let store_config = StoreConfig :: new (
26
- SpinKeyValueStore :: RUNTIME_CONFIG_TYPE . to_string ( ) ,
27
- default_config,
28
- ) ?;
29
- test_resolver. add_default_store ( "default" , store_config) ;
30
- Ok ( ( test_resolver, tmp_dir) )
31
- }
32
-
33
18
#[ tokio:: test]
34
19
async fn default_key_value_works ( ) -> anyhow:: Result < ( ) > {
35
- let ( test_resolver, dir) = default_key_value_resolver ( ) ?;
20
+ let mut test_resolver = RuntimeConfigResolver :: new ( ) ;
21
+ test_resolver. register_store_type ( SpinKeyValueStore :: new ( None ) ) ?;
22
+ test_resolver. add_default_store :: < SpinKeyValueStore > ( "default" , Default :: default ( ) ) ?;
36
23
let factors = TestFactors {
37
24
key_value : KeyValueFactor :: new ( test_resolver) ,
38
25
} ;
@@ -47,16 +34,14 @@ async fn default_key_value_works() -> anyhow::Result<()> {
47
34
state. key_value. allowed_stores( ) ,
48
35
& [ "default" . into( ) ] . into_iter( ) . collect:: <HashSet <_>>( )
49
36
) ;
50
- // Ensure the database directory is created
51
- assert ! ( dir. path( ) . exists( ) ) ;
52
37
Ok ( ( ) )
53
38
}
54
39
55
40
async fn run_test_with_config_and_stores_for_label (
56
41
runtime_config : Option < toml:: Table > ,
57
42
store_types : Vec < impl MakeKeyValueStore > ,
58
43
labels : Vec < & str > ,
59
- ) -> anyhow:: Result < ( ) > {
44
+ ) -> anyhow:: Result < TestFactorsInstanceState > {
60
45
let mut test_resolver = RuntimeConfigResolver :: new ( ) ;
61
46
for store_type in store_types {
62
47
test_resolver. register_store_type ( store_type) ?;
@@ -79,7 +64,7 @@ async fn run_test_with_config_and_stores_for_label(
79
64
state. key_value. allowed_stores( ) . iter( ) . collect:: <Vec <_>>( )
80
65
) ;
81
66
82
- Ok ( ( ) )
67
+ Ok ( state )
83
68
}
84
69
85
70
#[ tokio:: test]
@@ -94,7 +79,8 @@ async fn overridden_default_key_value_works() -> anyhow::Result<()> {
94
79
vec ! [ RedisKeyValueStore :: new( ) ] ,
95
80
vec ! [ "default" ] ,
96
81
)
97
- . await
82
+ . await ?;
83
+ Ok ( ( ) )
98
84
}
99
85
100
86
#[ tokio:: test]
@@ -105,52 +91,69 @@ async fn custom_spin_key_value_works() -> anyhow::Result<()> {
105
91
} ;
106
92
run_test_with_config_and_stores_for_label (
107
93
Some ( runtime_config) ,
108
- vec ! [ SpinKeyValueStore :: new(
109
- std:: env:: current_dir( ) . context( "failed to get current directory" ) ?,
110
- ) ] ,
94
+ vec ! [ SpinKeyValueStore :: new( None ) ] ,
111
95
vec ! [ "custom" ] ,
112
96
)
113
- . await
97
+ . await ?;
98
+ Ok ( ( ) )
114
99
}
115
100
116
- #[ tokio:: test]
101
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
117
102
async fn custom_spin_key_value_works_with_absolute_path ( ) -> anyhow:: Result < ( ) > {
118
103
let tmp_dir = tempdir:: TempDir :: new ( "example" ) ?;
119
- let path = tmp_dir. path ( ) . join ( "custom.db" ) ;
120
- let path_str = path. to_str ( ) . unwrap ( ) ;
104
+ let db_path = tmp_dir. path ( ) . join ( "foo/custom.db" ) ;
105
+ // Check that the db does not exist yet - it will exist by the end of the test
106
+ assert ! ( !db_path. exists( ) ) ;
107
+
108
+ let path_str = db_path. to_str ( ) . unwrap ( ) ;
121
109
let runtime_config = toml:: toml! {
122
110
[ key_value_store. custom]
123
111
type = "spin"
124
112
path = path_str
125
113
} ;
126
- run_test_with_config_and_stores_for_label (
114
+ let mut state = run_test_with_config_and_stores_for_label (
127
115
Some ( runtime_config) ,
128
- vec ! [ SpinKeyValueStore :: new(
116
+ vec ! [ SpinKeyValueStore :: new( Some (
129
117
std:: env:: current_dir( ) . context( "failed to get current directory" ) ?,
130
- ) ] ,
118
+ ) ) ] ,
131
119
vec ! [ "custom" ] ,
132
120
)
133
121
. await ?;
134
- assert ! ( tmp_dir. path( ) . exists( ) ) ;
122
+
123
+ // Actually et a key since store creation is lazy
124
+ let store = state. key_value . open ( "custom" . to_owned ( ) ) . await ??;
125
+ let _ = state. key_value . get ( store, "foo" . to_owned ( ) ) . await ??;
126
+
127
+ // Check that the parent has been created
128
+ assert ! ( db_path. exists( ) ) ;
135
129
Ok ( ( ) )
136
130
}
137
131
138
- #[ tokio:: test]
132
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
139
133
async fn custom_spin_key_value_works_with_relative_path ( ) -> anyhow:: Result < ( ) > {
140
134
let tmp_dir = tempdir:: TempDir :: new ( "example" ) ?;
141
- let path = tmp_dir. path ( ) . to_owned ( ) ;
135
+ let db_path = tmp_dir. path ( ) . join ( "custom.db" ) ;
136
+ // Check that the db does not exist yet - it will exist by the end of the test
137
+ assert ! ( !db_path. exists( ) ) ;
138
+
142
139
let runtime_config = toml:: toml! {
143
140
[ key_value_store. custom]
144
141
type = "spin"
145
142
path = "custom.db"
146
143
} ;
147
- run_test_with_config_and_stores_for_label (
144
+ let mut state = run_test_with_config_and_stores_for_label (
148
145
Some ( runtime_config) ,
149
- vec ! [ SpinKeyValueStore :: new( path) ] ,
146
+ vec ! [ SpinKeyValueStore :: new( Some ( tmp_dir . path( ) . to_owned ( ) ) ) ] ,
150
147
vec ! [ "custom" ] ,
151
148
)
152
149
. await ?;
153
- assert ! ( tmp_dir. path( ) . exists( ) ) ;
150
+
151
+ // Actually et a key since store creation is lazy
152
+ let store = state. key_value . open ( "custom" . to_owned ( ) ) . await ??;
153
+ let _ = state. key_value . get ( store, "foo" . to_owned ( ) ) . await ??;
154
+
155
+ // Check that the correct store in the config was chosen by verifying the existence of the DB
156
+ assert ! ( db_path. exists( ) ) ;
154
157
Ok ( ( ) )
155
158
}
156
159
@@ -166,64 +169,75 @@ async fn custom_redis_key_value_works() -> anyhow::Result<()> {
166
169
vec ! [ RedisKeyValueStore :: new( ) ] ,
167
170
vec ! [ "custom" ] ,
168
171
)
169
- . await
172
+ . await ?;
173
+ Ok ( ( ) )
170
174
}
171
175
172
176
#[ tokio:: test]
173
177
async fn misconfigured_spin_key_value_fails ( ) -> anyhow:: Result < ( ) > {
178
+ let tmp_dir = tempdir:: TempDir :: new ( "example" ) ?;
174
179
let runtime_config = toml:: toml! {
175
180
[ key_value_store. custom]
176
181
type = "spin"
177
182
path = "/$$&/bad/path/foo.db"
178
183
} ;
179
- assert ! ( run_test_with_config_and_stores_for_label(
184
+ let result = run_test_with_config_and_stores_for_label (
180
185
Some ( runtime_config) ,
181
- vec![ SpinKeyValueStore :: new(
182
- std:: env:: current_dir( ) . context( "failed to get current directory" ) ?
183
- ) ] ,
184
- vec![ "custom" ]
186
+ vec ! [ SpinKeyValueStore :: new( Some ( tmp_dir. path( ) . to_owned( ) ) ) ] ,
187
+ vec ! [ "custom" ] ,
185
188
)
186
- . await
187
- . is_err( ) ) ;
189
+ . await ;
190
+ // TODO(rylev): This only fails on my machine due to a read-only file system error.
191
+ // We should consider adding a check for the error message.
192
+ assert ! ( result. is_err( ) ) ;
188
193
Ok ( ( ) )
189
194
}
190
195
191
- #[ tokio:: test]
192
- async fn multiple_custom_key_value_uses_first_store ( ) -> anyhow:: Result < ( ) > {
196
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
197
+ // TODO(rylev): consider removing this test as it is really only a consequence of
198
+ // toml deserialization and not a feature of the key-value store.
199
+ async fn multiple_custom_key_value_uses_second_store ( ) -> anyhow:: Result < ( ) > {
193
200
let tmp_dir = tempdir:: TempDir :: new ( "example" ) ?;
201
+ let db_path = tmp_dir. path ( ) . join ( "custom.db" ) ;
202
+ // Check that the db does not exist yet - it will exist by the end of the test
203
+ assert ! ( !db_path. exists( ) ) ;
204
+
194
205
let mut test_resolver = RuntimeConfigResolver :: new ( ) ;
195
206
test_resolver. register_store_type ( RedisKeyValueStore :: new ( ) ) ?;
196
- test_resolver. register_store_type ( SpinKeyValueStore :: new ( tmp_dir. path ( ) . to_owned ( ) ) ) ?;
207
+ test_resolver. register_store_type ( SpinKeyValueStore :: new ( Some ( tmp_dir. path ( ) . to_owned ( ) ) ) ) ?;
197
208
let test_resolver = Arc :: new ( test_resolver) ;
198
209
let factors = TestFactors {
199
210
key_value : KeyValueFactor :: new ( test_resolver. clone ( ) ) ,
200
211
} ;
212
+ let runtime_config = toml:: toml! {
213
+ [ key_value_store. custom]
214
+ type = "redis"
215
+ url = "redis://localhost:6379"
216
+
217
+ [ key_value_store. custom]
218
+ type = "spin"
219
+ path = "custom.db"
220
+
221
+ } ;
201
222
let env = TestEnvironment :: new ( factors)
202
223
. extend_manifest ( toml ! {
203
224
[ component. test-component]
204
225
source = "does-not-exist.wasm"
205
226
key_value_stores = [ "custom" ]
206
227
} )
207
- . runtime_config ( TomlConfig :: new (
208
- test_resolver,
209
- Some ( toml:: toml! {
210
- [ key_value_store. custom]
211
- type = "spin"
212
- path = "custom.db"
228
+ . runtime_config ( TomlConfig :: new ( test_resolver, Some ( runtime_config) ) ) ?;
229
+ let mut state = env. build_instance_state ( ) . await ?;
213
230
214
- [ key_value_store. custom]
215
- type = "redis"
216
- url = "redis://localhost:6379"
217
- } ) ,
218
- ) ) ?;
219
- let state = env. build_instance_state ( ) . await ?;
231
+ // Actually et a key since store creation is lazy
232
+ let store = state. key_value . open ( "custom" . to_owned ( ) ) . await ??;
233
+ let _ = state. key_value . get ( store, "foo" . to_owned ( ) ) . await ??;
220
234
221
235
assert_eq ! (
222
236
state. key_value. allowed_stores( ) ,
223
237
& [ "custom" . into( ) ] . into_iter( ) . collect:: <HashSet <_>>( )
224
238
) ;
225
- // Check that the first store in the config was chosen by verifying the existence of the DB directory
226
- assert ! ( tmp_dir . path ( ) . exists( ) ) ;
239
+ // Check that the correct store in the config was chosen by verifying the existence of the DB
240
+ assert ! ( db_path . exists( ) ) ;
227
241
Ok ( ( ) )
228
242
}
229
243
0 commit comments