@@ -2,11 +2,11 @@ use std::sync::Arc;
2
2
3
3
use anyhow:: Result ;
4
4
use config:: Config ;
5
- use display:: { Display , Size } ;
5
+ use display:: Display ;
6
6
use git:: Repository ;
7
7
use input:: { EventHandler , EventReaderFn } ;
8
8
use parking_lot:: Mutex ;
9
- use runtime:: Runtime ;
9
+ use runtime:: { Runtime , Threadable } ;
10
10
use todo_file:: TodoFile ;
11
11
use view:: View ;
12
12
@@ -20,28 +20,23 @@ use crate::{
20
20
Exit ,
21
21
} ;
22
22
23
- pub ( crate ) struct Application < ModuleProvider , EventProvider , Tui >
24
- where
25
- ModuleProvider : module:: ModuleProvider + Send + ' static ,
26
- EventProvider : EventReaderFn ,
27
- Tui : display:: Tui + ' static ,
23
+ pub ( crate ) struct Application < ModuleProvider >
24
+ where ModuleProvider : module:: ModuleProvider + Send + ' static
28
25
{
29
26
_config : Config ,
30
27
_repository : Repository ,
31
- todo_file : Arc < Mutex < TodoFile > > ,
32
- view : View < Tui > ,
33
- event_provider : EventProvider ,
34
- initial_display_size : Size ,
35
- module_handler : ModuleHandler < ModuleProvider > ,
28
+ process : Process < ModuleProvider > ,
29
+ threads : Option < Vec < Box < dyn Threadable > > > ,
36
30
}
37
31
38
- impl < ModuleProvider , EventProvider , Tui > Application < ModuleProvider , EventProvider , Tui >
39
- where
40
- ModuleProvider : module:: ModuleProvider + Send + ' static ,
41
- EventProvider : EventReaderFn ,
42
- Tui : display:: Tui + Send + ' static ,
32
+ impl < ModuleProvider > Application < ModuleProvider >
33
+ where ModuleProvider : module:: ModuleProvider + Send + ' static
43
34
{
44
- pub ( crate ) fn new ( args : & Args , event_provider : EventProvider , tui : Tui ) -> Result < Self , Exit > {
35
+ pub ( crate ) fn new < EventProvider , Tui > ( args : & Args , event_provider : EventProvider , tui : Tui ) -> Result < Self , Exit >
36
+ where
37
+ EventProvider : EventReaderFn ,
38
+ Tui : display:: Tui + Send + ' static ,
39
+ {
45
40
let filepath = Self :: filepath_from_args ( args) ?;
46
41
let repository = Self :: open_repository ( ) ?;
47
42
let config = Self :: load_config ( & repository) ?;
@@ -65,39 +60,46 @@ where
65
60
. as_str ( ) ,
66
61
) ;
67
62
68
- Ok ( Self {
69
- _config : config,
70
- _repository : repository,
71
- todo_file,
72
- view,
73
- event_provider,
74
- module_handler,
75
- initial_display_size,
76
- } )
77
- }
78
-
79
- pub ( crate ) fn run_until_finished ( self ) -> Result < ( ) , Exit > {
63
+ let mut threads: Vec < Box < dyn Threadable > > = vec ! [ ] ;
80
64
let runtime = Runtime :: new ( ) ;
81
65
82
- let mut input_threads = events:: Thread :: new ( self . event_provider ) ;
66
+ let input_threads = events:: Thread :: new ( event_provider) ;
83
67
let input_state = input_threads. state ( ) ;
68
+ threads. push ( Box :: new ( input_threads) ) ;
84
69
85
- let mut view_threads = view:: Thread :: new ( self . view ) ;
70
+ let view_threads = view:: Thread :: new ( view) ;
86
71
let view_state = view_threads. state ( ) ;
72
+ threads. push ( Box :: new ( view_threads) ) ;
87
73
88
74
let process = Process :: new (
89
- self . initial_display_size ,
90
- self . todo_file ,
91
- self . module_handler ,
75
+ initial_display_size,
76
+ todo_file,
77
+ module_handler,
92
78
input_state,
93
79
view_state,
94
80
runtime. statuses ( ) ,
95
81
) ;
96
- let mut process_threads = process:: Thread :: new ( process. clone ( ) ) ;
82
+ let process_threads = process:: Thread :: new ( process. clone ( ) ) ;
83
+ threads. push ( Box :: new ( process_threads) ) ;
84
+
85
+ Ok ( Self {
86
+ _config : config,
87
+ _repository : repository,
88
+ process,
89
+ threads : Some ( threads) ,
90
+ } )
91
+ }
92
+
93
+ pub ( crate ) fn run_until_finished ( & mut self ) -> Result < ( ) , Exit > {
94
+ let Some ( mut threads) = self . threads . take ( ) else {
95
+ return Err ( Exit :: new ( ExitStatus :: StateError , "Attempt made to run application a second time" ) ) ;
96
+ } ;
97
+
98
+ let runtime = Runtime :: new ( ) ;
97
99
98
- runtime . register ( & mut input_threads ) ;
99
- runtime. register ( & mut view_threads ) ;
100
- runtime . register ( & mut process_threads ) ;
100
+ for thread in & mut threads {
101
+ runtime. register ( thread . as_mut ( ) ) ;
102
+ }
101
103
102
104
runtime. join ( ) . map_err ( |err| {
103
105
Exit :: new (
@@ -106,7 +108,7 @@ where
106
108
)
107
109
} ) ?;
108
110
109
- let exit_status = process. exit_status ( ) ;
111
+ let exit_status = self . process . exit_status ( ) ;
110
112
if exit_status != ExitStatus :: Good {
111
113
return Err ( Exit :: from ( exit_status) ) ;
112
114
}
@@ -165,8 +167,9 @@ mod tests {
165
167
use std:: ffi:: OsString ;
166
168
167
169
use claim:: assert_ok;
168
- use display:: testutil:: CrossTerm ;
170
+ use display:: { testutil:: CrossTerm , Size } ;
169
171
use input:: { KeyCode , KeyEvent , KeyModifiers } ;
172
+ use runtime:: { Installer , RuntimeError } ;
170
173
171
174
use super :: * ;
172
175
use crate :: {
@@ -200,7 +203,7 @@ mod tests {
200
203
#[ serial_test:: serial]
201
204
fn load_filepath_from_args_failure ( ) {
202
205
let event_provider = create_event_reader ( || Ok ( None ) ) ;
203
- let application: Result < Application < TestModuleProvider < DefaultTestModule > , _ , _ > , Exit > =
206
+ let application: Result < Application < TestModuleProvider < DefaultTestModule > > , Exit > =
204
207
Application :: new ( & args ( & [ ] ) , event_provider, create_mocked_crossterm ( ) ) ;
205
208
let exit = application_error ! ( application) ;
206
209
assert_eq ! ( exit. get_status( ) , & ExitStatus :: StateError ) ;
@@ -217,7 +220,7 @@ mod tests {
217
220
fn load_repository_failure ( ) {
218
221
let _ = set_git_directory ( "fixtures/not-a-repository" ) ;
219
222
let event_provider = create_event_reader ( || Ok ( None ) ) ;
220
- let application: Result < Application < TestModuleProvider < DefaultTestModule > , _ , _ > , Exit > =
223
+ let application: Result < Application < TestModuleProvider < DefaultTestModule > > , Exit > =
221
224
Application :: new ( & args ( & [ "todofile" ] ) , event_provider, create_mocked_crossterm ( ) ) ;
222
225
let exit = application_error ! ( application) ;
223
226
assert_eq ! ( exit. get_status( ) , & ExitStatus :: StateError ) ;
@@ -234,7 +237,7 @@ mod tests {
234
237
fn load_config_failure ( ) {
235
238
let _ = set_git_directory ( "fixtures/invalid-config" ) ;
236
239
let event_provider = create_event_reader ( || Ok ( None ) ) ;
237
- let application: Result < Application < TestModuleProvider < DefaultTestModule > , _ , _ > , Exit > =
240
+ let application: Result < Application < TestModuleProvider < DefaultTestModule > > , Exit > =
238
241
Application :: new ( & args ( & [ "rebase-todo" ] ) , event_provider, create_mocked_crossterm ( ) ) ;
239
242
let exit = application_error ! ( application) ;
240
243
assert_eq ! ( exit. get_status( ) , & ExitStatus :: ConfigError ) ;
@@ -245,7 +248,7 @@ mod tests {
245
248
fn load_todo_file_load_error ( ) {
246
249
let _ = set_git_directory ( "fixtures/simple" ) ;
247
250
let event_provider = create_event_reader ( || Ok ( None ) ) ;
248
- let application: Result < Application < TestModuleProvider < DefaultTestModule > , _ , _ > , Exit > =
251
+ let application: Result < Application < TestModuleProvider < DefaultTestModule > > , Exit > =
249
252
Application :: new ( & args ( & [ "does-not-exist" ] ) , event_provider, create_mocked_crossterm ( ) ) ;
250
253
let exit = application_error ! ( application) ;
251
254
assert_eq ! ( exit. get_status( ) , & ExitStatus :: FileReadError ) ;
@@ -257,7 +260,7 @@ mod tests {
257
260
let git_dir = set_git_directory ( "fixtures/simple" ) ;
258
261
let rebase_todo = format ! ( "{git_dir}/rebase-todo-noop" ) ;
259
262
let event_provider = create_event_reader ( || Ok ( None ) ) ;
260
- let application: Result < Application < TestModuleProvider < DefaultTestModule > , _ , _ > , Exit > = Application :: new (
263
+ let application: Result < Application < TestModuleProvider < DefaultTestModule > > , Exit > = Application :: new (
261
264
& args ( & [ rebase_todo. as_str ( ) ] ) ,
262
265
event_provider,
263
266
create_mocked_crossterm ( ) ,
@@ -272,7 +275,7 @@ mod tests {
272
275
let git_dir = set_git_directory ( "fixtures/simple" ) ;
273
276
let rebase_todo = format ! ( "{git_dir}/rebase-todo-empty" ) ;
274
277
let event_provider = create_event_reader ( || Ok ( None ) ) ;
275
- let application: Result < Application < TestModuleProvider < DefaultTestModule > , _ , _ > , Exit > = Application :: new (
278
+ let application: Result < Application < TestModuleProvider < DefaultTestModule > > , Exit > = Application :: new (
276
279
& args ( & [ rebase_todo. as_str ( ) ] ) ,
277
280
event_provider,
278
281
create_mocked_crossterm ( ) ,
@@ -293,7 +296,7 @@ mod tests {
293
296
let git_dir = set_git_directory ( "fixtures/simple" ) ;
294
297
let rebase_todo = format ! ( "{git_dir}/rebase-todo" ) ;
295
298
let event_provider = create_event_reader ( || Ok ( Some ( Event :: Key ( KeyEvent :: from ( KeyCode :: Char ( 'W' ) ) ) ) ) ) ;
296
- let application: Application < Modules , _ , _ > = Application :: new (
299
+ let mut application: Application < Modules > = Application :: new (
297
300
& args ( & [ rebase_todo. as_str ( ) ] ) ,
298
301
event_provider,
299
302
create_mocked_crossterm ( ) ,
@@ -302,6 +305,42 @@ mod tests {
302
305
assert_ok ! ( application. run_until_finished( ) ) ;
303
306
}
304
307
308
+ #[ test]
309
+ #[ serial_test:: serial]
310
+ fn run_join_error ( ) {
311
+ struct FailingThread ;
312
+ impl Threadable for FailingThread {
313
+ fn install ( & self , installer : & Installer ) {
314
+ installer. spawn ( "THREAD" , |notifier| {
315
+ move || {
316
+ notifier. error ( RuntimeError :: ThreadSpawnError ( String :: from ( "Error" ) ) ) ;
317
+ }
318
+ } ) ;
319
+ }
320
+ }
321
+
322
+ let git_dir = set_git_directory ( "fixtures/simple" ) ;
323
+ let rebase_todo = format ! ( "{git_dir}/rebase-todo" ) ;
324
+ let event_provider = create_event_reader ( || Ok ( Some ( Event :: Key ( KeyEvent :: from ( KeyCode :: Char ( 'W' ) ) ) ) ) ) ;
325
+ let mut application: Application < Modules > = Application :: new (
326
+ & args ( & [ rebase_todo. as_str ( ) ] ) ,
327
+ event_provider,
328
+ create_mocked_crossterm ( ) ,
329
+ )
330
+ . unwrap ( ) ;
331
+
332
+ application. threads = Some ( vec ! [ Box :: new( FailingThread { } ) ] ) ;
333
+
334
+ let exit = application. run_until_finished ( ) . unwrap_err ( ) ;
335
+ assert_eq ! ( exit. get_status( ) , & ExitStatus :: StateError ) ;
336
+ assert ! (
337
+ exit. get_message( )
338
+ . as_ref( )
339
+ . unwrap( )
340
+ . starts_with( "Failed to join runtime:" )
341
+ ) ;
342
+ }
343
+
305
344
#[ test]
306
345
#[ serial_test:: serial]
307
346
fn run_until_finished_kill ( ) {
@@ -313,7 +352,7 @@ mod tests {
313
352
KeyModifiers :: CONTROL ,
314
353
) ) ) )
315
354
} ) ;
316
- let application: Application < Modules , _ , _ > = Application :: new (
355
+ let mut application: Application < Modules > = Application :: new (
317
356
& args ( & [ rebase_todo. as_str ( ) ] ) ,
318
357
event_provider,
319
358
create_mocked_crossterm ( ) ,
@@ -322,4 +361,25 @@ mod tests {
322
361
let exit = application. run_until_finished ( ) . unwrap_err ( ) ;
323
362
assert_eq ! ( exit. get_status( ) , & ExitStatus :: Kill ) ;
324
363
}
364
+
365
+ #[ test]
366
+ #[ serial_test:: serial]
367
+ fn run_error_on_second_attempt ( ) {
368
+ let git_dir = set_git_directory ( "fixtures/simple" ) ;
369
+ let rebase_todo = format ! ( "{git_dir}/rebase-todo" ) ;
370
+ let event_provider = create_event_reader ( || Ok ( Some ( Event :: Key ( KeyEvent :: from ( KeyCode :: Char ( 'W' ) ) ) ) ) ) ;
371
+ let mut application: Application < Modules > = Application :: new (
372
+ & args ( & [ rebase_todo. as_str ( ) ] ) ,
373
+ event_provider,
374
+ create_mocked_crossterm ( ) ,
375
+ )
376
+ . unwrap ( ) ;
377
+ assert_ok ! ( application. run_until_finished( ) ) ;
378
+ let exit = application. run_until_finished ( ) . unwrap_err ( ) ;
379
+ assert_eq ! ( exit. get_status( ) , & ExitStatus :: StateError ) ;
380
+ assert_eq ! (
381
+ exit. get_message( ) . as_ref( ) . unwrap( ) ,
382
+ "Attempt made to run application a second time"
383
+ ) ;
384
+ }
325
385
}
0 commit comments