gloo 7.1 → 7.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +0 -2
  3. data/docs/application.md +9 -0
  4. data/docs/development.md +22 -2
  5. data/docs/iterators.md +25 -1
  6. data/docs/language_syntax.md +1 -1
  7. data/docs/plugins.md +1 -1
  8. data/docs/verbs.md +2 -1
  9. data/lib/VERSION +1 -1
  10. data/lib/VERSION_NOTES +16 -0
  11. data/lib/gloo/app/args.rb +3 -5
  12. data/lib/gloo/app/log.rb +2 -2
  13. data/lib/gloo/core/invoker.rb +20 -7
  14. data/lib/gloo/core/pn.rb +5 -0
  15. data/lib/gloo/core/verb.rb +14 -3
  16. data/lib/gloo/exec/exec_env.rb +6 -3
  17. data/lib/gloo/exec/script.rb +7 -0
  18. data/lib/gloo/objs/basic/script.rb +9 -0
  19. data/lib/gloo/objs/ctrl/each.rb +5 -3
  20. data/lib/gloo/objs/ctrl/each_child.rb +6 -2
  21. data/lib/gloo/objs/ctrl/each_dir.rb +1 -1
  22. data/lib/gloo/objs/ctrl/each_file.rb +1 -1
  23. data/lib/gloo/objs/ctrl/each_line.rb +4 -3
  24. data/lib/gloo/objs/ctrl/each_word.rb +1 -1
  25. data/lib/gloo/objs/ctrl/repeat.rb +5 -3
  26. data/lib/gloo/objs/str_utils/cipher.rb +10 -2
  27. data/lib/gloo/persist/declaration_ledger.rb +6 -6
  28. data/lib/gloo/persist/disc_mech.rb +38 -20
  29. data/lib/gloo/persist/file_loader.rb +1 -1
  30. data/lib/gloo/persist/persist_man.rb +1 -1
  31. data/lib/gloo/verbs/break.rb +7 -2
  32. data/lib/gloo/verbs/create.rb +4 -2
  33. data/lib/gloo/verbs/eval.rb +5 -4
  34. data/lib/gloo/verbs/invoke.rb +7 -6
  35. data/lib/gloo/verbs/load.rb +7 -1
  36. data/lib/gloo/verbs/log.rb +8 -4
  37. data/lib/gloo/verbs/show.rb +15 -10
  38. data/lib/gloo.rb +18 -4
  39. data/lib/run.rb +3 -9
  40. data/test.gloo/app/embed.test.gloo +31 -0
  41. data/test.gloo/app/fixtures/embed_harness.rb +15 -0
  42. data/test.gloo/ctrl/each.test.gloo +72 -0
  43. data/test.gloo/lang/exceptions.test.gloo +22 -0
  44. data/test.gloo/objs/cipher.test.gloo +27 -0
  45. data/test.gloo/objs/int.test.gloo +6 -3
  46. data/test.gloo/verbs/break.test.gloo +66 -0
  47. data/test.gloo/verbs/create.test.gloo +25 -0
  48. data/test.gloo/verbs/eval.test.gloo +8 -0
  49. data/test.gloo/verbs/invoke.test.gloo +50 -0
  50. data/test.gloo/verbs/load.test.gloo +34 -0
  51. data/test.gloo/verbs/log.test.gloo +16 -0
  52. data/test.gloo/verbs/show.test.gloo +32 -0
  53. metadata +3 -1
@@ -28,22 +28,30 @@ module Gloo
28
28
  end
29
29
 
30
30
  #
31
- # Get all the gloo files in the folder (partial path).
31
+ # Get all the gloo files in the folder (partial path). The folder
32
+ # can end with the start of a file name, as a prefix (folder/a).
32
33
  #
33
34
  def get_all_files_in( folder )
34
- pns = []
35
- dir = File.join( @engine.settings.project_path, folder )
35
+ dir = folder_for_files( folder )
36
+ return Dir.glob( "#{dir}*.gloo" )
37
+ end
36
38
 
37
- unless Dir.exist?( dir )
38
- @log.debug "Folder does not exist in project: #{folder}"
39
- dir = File.join( @engine.settings.user_root, folder )
40
- @log.debug "Looking in gloo home? found == #{Dir.exist?( dir )}"
41
- end
39
+ #
40
+ # Where to look for a folder of files: an absolute or ~ folder as
41
+ # given (~ expanded), anything else in the project folder, or if it
42
+ # isn't there, in the gloo home.
43
+ #
44
+ def folder_for_files( folder )
45
+ return folder.sub( '~', Dir.home ) if folder.start_with?( '~' )
46
+ return folder if File.absolute_path?( folder )
42
47
 
43
- Dir.glob( "#{dir}*.gloo" ).each do |f|
44
- pns << f
45
- end
46
- return pns
48
+ dir = File.join( @engine.settings.project_path, folder )
49
+ return dir if Dir.exist?( dir )
50
+
51
+ @log.debug "Folder does not exist in project: #{folder}"
52
+ dir = File.join( @engine.settings.user_root, folder )
53
+ @log.debug "Looking in gloo home? found == #{Dir.exist?( dir )}"
54
+ return dir
47
55
  end
48
56
 
49
57
  #
@@ -65,23 +73,33 @@ module Gloo
65
73
  return true
66
74
  end
67
75
 
68
- #
69
- # Expand a single file path.
70
- #
76
+ #
77
+ # Expand a single file path. The .gloo extension is optional: it's
78
+ # added when the name doesn't end with it, for a full or ~ path
79
+ # and for a name in the project or gloo home.
80
+ #
71
81
  def expand( name )
72
- # Try full path
82
+ # Try the name as given: a full path, or relative to the working
83
+ # folder (eg. gloo hello_world.gloo).
73
84
  ext_path = File.expand_path( name )
74
85
  return [ ext_path ] if self.valid?( ext_path )
75
86
 
76
- # Try in user root, projects
77
- full_name = "#{name}#{file_ext}"
87
+ full_name = name.end_with?( file_ext ) ? name : "#{name}#{file_ext}"
88
+
89
+ # A full or ~ path without the extension.
90
+ if name.start_with?( '~' ) || File.absolute_path?( name )
91
+ ext_path = File.expand_path( full_name )
92
+ return [ ext_path ] if self.valid?( ext_path )
93
+ end
94
+
95
+ # Try in projects
78
96
  pn = File.join( @engine.settings.project_path, full_name )
79
97
  return [ pn ] if self.valid?( pn )
80
-
98
+
81
99
  # Try in user root
82
100
  pn = File.join( @engine.settings.user_root, full_name )
83
101
  return [ pn ] if self.valid?( pn )
84
-
102
+
85
103
  return nil
86
104
  end
87
105
 
@@ -53,7 +53,7 @@ module Gloo
53
53
  @trivia = Gloo::Persist::TriviaBuffer.new
54
54
  @body = Gloo::Persist::ScriptBodyCollector.new
55
55
  @shorthand = Gloo::Persist::ShorthandExpander.new( engine )
56
- @ledger = Gloo::Persist::DeclarationLedger.new( engine, pn )
56
+ @ledger = Gloo::Persist::DeclarationLedger.new( engine )
57
57
  @in_block = false
58
58
  @block_value = ''
59
59
  @body_started = false
@@ -396,7 +396,7 @@ module Gloo
396
396
  #
397
397
  def warn_if_dirty( fs )
398
398
  saver = Gloo::Persist::FileSaver.new( @engine, fs.pn, fs.obj, fs.source_doc )
399
- @engine.log.warn "#{RELOAD_DIRTY_WARNING}#{fs.pn}" if saver.dirty?
399
+ @engine.warn "#{RELOAD_DIRTY_WARNING}#{fs.pn}" if saver.dirty?
400
400
  end
401
401
 
402
402
  end
@@ -51,11 +51,16 @@ module Gloo
51
51
  {
52
52
  :name => KEYWORD,
53
53
  :shortcut => KEYWORD_SHORT,
54
- :description => 'Break out of the current script, stop running.',
54
+ :description => 'Break out of the current script, stop running. ' \
55
+ 'If that script is the do script of a loop (repeat, or any ' \
56
+ 'kind of each), the whole loop ends.',
55
57
  :syntax => [ 'break' ],
56
58
  :result => 'No more commands are executed in the currently ' \
57
59
  'running script. If there are other scripts in the stack, ' \
58
- 'they will resume normally.',
60
+ 'they will resume normally. In a loop\'s do script, the loop ' \
61
+ 'ends too and runs no more passes; the script that ran the ' \
62
+ 'loop carries on. A break in a script the do script runs ' \
63
+ '(eg. with run) ends only that script, not the loop.',
59
64
  :examples => <<~EXAMPLES.strip
60
65
  #
61
66
  # Break out of a script.
@@ -111,7 +111,9 @@ module Gloo
111
111
  'value. Both type and value are optional when creating an object.',
112
112
  :syntax => [ 'create {new.object.path} as {type} : {value}' ],
113
113
  :parameters => [
114
- '{new.object.path} — The path and name of the new object.',
114
+ '{new.object.path} — The path and name of the new object. ' \
115
+ 'Like any path, it can start from the root, from here in ' \
116
+ 'a script (^.box.new, ^^.new), or from the context (@.new).',
115
117
  '{type} — The type of the new object. Optional; if not provided the object will be untyped.',
116
118
  "{value} — The initial value for the new object. Optional; if not provided the object will have the default value for the type."
117
119
  ],
@@ -123,7 +125,7 @@ module Gloo
123
125
  "to the existing object's current value.",
124
126
  :errors => [
125
127
  "#{NO_NAME_ERR} — The name of the object was not specified and the object cannot be created.",
126
- "Could not create '{path}': Object '{parent.path}' was not found. — The parent container named in the path does not exist. The path is root-relative and every container above the new object must already exist. Nothing is created, and it is false.",
128
+ "Could not create '{path}': Object '{parent.path}' was not found. — The parent container named in the path does not exist. Every container above the new object must already exist. Nothing is created, and it is false.",
127
129
  "#{format( BAD_NAME_ERR, '{name}' )} — The name has parentheses or quotes, or there are extra words between the name and as / : — create takes a name as written. Nothing is created, and it is false."
128
130
  ],
129
131
  :examples => <<~EXAMPLES.strip
@@ -30,9 +30,8 @@ module Gloo
30
30
  #
31
31
  def run
32
32
  if @tokens.token_count > 1
33
- expr = Gloo::Expr::Expression.new( @engine, @tokens.params )
34
- result = expr.evaluate
35
- @engine.heap.it.set_to result
33
+ result, ok = evaluate_expr( @tokens.params )
34
+ @engine.heap.it.set_to( ok ? result : false )
36
35
  else
37
36
  @engine.heap.it.set_to true
38
37
  end
@@ -61,7 +60,9 @@ module Gloo
61
60
  '{expression} — The expression that will be evaluated. The expression is optional. If not provided, eval will result in true.'
62
61
  ],
63
62
  :result => 'The result of the expression evaluation is put ' \
64
- 'into it. No other action is performed.',
63
+ 'into it. No other action is performed. If the expression ' \
64
+ 'fails (eg. it names a missing object), the error is ' \
65
+ 'reported and it is false.',
65
66
  :examples => <<~EXAMPLES.strip,
66
67
  > eval "me"
67
68
  > eval "hello " "world"
@@ -60,7 +60,8 @@ module Gloo
60
60
  '{path.to.function} — The function we want to invoke.',
61
61
  '{params} — The list of parameters to the function.'
62
62
  ],
63
- :result => 'The result of the function is put into it. The ' \
63
+ :result => 'The result of the function is put into it; if ' \
64
+ 'anything goes wrong, it is false. The ' \
64
65
  'inline forms (invoke(...), ~>(...)) can be used anywhere ' \
65
66
  'an expression is evaluated - show, put ... into, if, ' \
66
67
  'unless, eval, and more - and evaluate to the result ' \
@@ -69,13 +70,13 @@ module Gloo
69
70
  "#{Gloo::Core::Invoker::NO_TARGET_ERR} — No function reference was given.",
70
71
  "Object '{path.to.function}' was not found. — The path doesn't resolve to any object.",
71
72
  "#{Gloo::Core::Invoker::NOT_FUNCTION_ERR}{path.to.function} — The path resolves to an object that isn't a function.",
72
- "#{Gloo::Core::Invoker::PARAM_COUNT_ERR}{path.to.function} (expected N, got N) — The number of params given doesn't match what the function declares."
73
+ "#{Gloo::Core::Invoker::PARAM_COUNT_ERR}{path.to.function} (expected N, got N) — The number of params given doesn't match what the function declares.",
74
+ "Object '{param}' was not found. — A param names an object that doesn't exist. The function isn't run."
73
75
  ],
74
76
  :notes => 'If the function itself fails during invocation ' \
75
- '(its on_invoke script hits an error), it is left ' \
76
- 'unchanged rather than being set to an unreliable result. ' \
77
- 'The underlying error is whatever was reported by the ' \
78
- 'failure inside the function. ' \
77
+ '(its on_invoke script hits an error), it is false rather ' \
78
+ 'than an unreliable result. The underlying error is ' \
79
+ 'whatever was reported by the failure inside the function. ' \
79
80
  'Inline call params are space-separated, same as the ' \
80
81
  'standalone verb - each one is evaluated as a single ' \
81
82
  'token (a literal or object reference), not a multi-token ' \
@@ -115,7 +115,12 @@ module Gloo
115
115
  'load [ext|lib] ( {expression} )'
116
116
  ],
117
117
  :parameters => [
118
- '{file_name} — Name of the object file that is to be loaded.',
118
+ '{file_name} — Name of the object file that is to be loaded: ' \
119
+ 'relative to the project, a full path, or starting with ~. ' \
120
+ 'The .gloo extension is optional. ' \
121
+ 'End it with * to load every .gloo file in a folder (a ' \
122
+ 'folder in the project, a full path, or one starting with ' \
123
+ '~); anything before the * is the start of the file names.',
119
124
  'reference type — By default, the type is file. File can be ' \
120
125
  'specified, but does not need to be. Use ext to load a ' \
121
126
  'User Extension. Use lib to load a Core Library.',
@@ -154,6 +159,7 @@ module Gloo
154
159
  > load my/project/file
155
160
  > load my/app/*
156
161
  > load ~/.my_app/settings.gloo
162
+ > load ~/.my_app/plugins/*
157
163
 
158
164
  # Load a file whose name is worked out
159
165
  > load ( $.gloo_tmp '/scratch.gloo' )
@@ -50,11 +50,12 @@ module Gloo
50
50
  # Write to the specified logger.
51
51
  #
52
52
  def write_to_log
53
- expr = Gloo::Expr::Expression.new( @engine, @tokens.params )
54
- result = expr.evaluate
53
+ # A failed expression is still logged as far as it got, but it
54
+ # is false.
55
+ result, ok = evaluate_expr( @tokens.params )
55
56
  level = log_level_specified( result )
56
57
  @engine.log.write result, level
57
- @engine.heap.it.set_to result
58
+ @engine.heap.it.set_to( ok ? result : false )
58
59
  end
59
60
 
60
61
  #
@@ -106,7 +107,10 @@ module Gloo
106
107
  'not specified, the message is written as DEBUG level.'
107
108
  ],
108
109
  :result => 'The message is written to the log at the ' \
109
- 'specified level. It will contain the log message.',
110
+ 'specified level. It will contain the log message. If the ' \
111
+ 'expression fails, the error is reported and what was ' \
112
+ 'worked out so far is still logged, but it is false. A ' \
113
+ 'level that can\'t be worked out falls back to debug.',
110
114
  :notes => "Use `log clear` from the CLI to clear out both the " \
111
115
  "standard log and the error log.\n\n" \
112
116
  "> log clear",
@@ -16,10 +16,11 @@ module Gloo
16
16
  #
17
17
  def run
18
18
  if @tokens.token_count > 1
19
- expr = Gloo::Expr::Expression.new( @engine, @tokens.params )
20
- result = expr.evaluate
19
+ # A failed expression is still shown as far as it got (a best
20
+ # guess, and handy when debugging), but it is false.
21
+ result, ok = evaluate_expr( @tokens.params )
21
22
  @engine.log.show get_formatted_string( result )
22
- @engine.heap.it.set_to result
23
+ @engine.heap.it.set_to( ok ? result : false )
23
24
  else
24
25
  @engine.log.show ''
25
26
  end
@@ -46,14 +47,16 @@ module Gloo
46
47
  private
47
48
 
48
49
  #
49
- # Get the formatted string.
50
+ # Get the formatted string: in the color given in the params, if
51
+ # any. A color that can't be worked out (already reported) shows
52
+ # the string plain.
50
53
  #
51
54
  def get_formatted_string( str )
52
55
  if @params&.token_count&.positive?
53
- expr = Gloo::Expr::Expression.new( @engine, @params.tokens )
54
- val = expr.evaluate
55
- color = val.to_sym
56
- return @engine.platform.get_colorized_string( str, color )
56
+ val, ok = evaluate_expr( @params.tokens )
57
+ return str unless ok && val
58
+
59
+ return @engine.platform.get_colorized_string( str, val.to_sym )
57
60
  end
58
61
  return str
59
62
  end
@@ -79,11 +82,13 @@ module Gloo
79
82
  '{target} — The object that we want to see. The target ' \
80
83
  'might be the path to an object, a literal or an ' \
81
84
  'expression to be evaluated before being shown.',
82
- '{color} — Optional color for the text.'
85
+ '{color} — Optional color for the text. If the color can\'t be worked out, the error is reported and the text is shown plain.'
83
86
  ],
84
87
  :result => "The object's value is shown, or the literal, or " \
85
88
  'the result of the evaluated expression is shown. It will ' \
86
- "contain the object's value.",
89
+ "contain the object's value. If the expression fails (eg. " \
90
+ 'it names a missing object), the error is reported and ' \
91
+ 'what was worked out so far is still shown, but it is false.',
87
92
  :examples => <<~EXAMPLES.strip,
88
93
  > show "me"
89
94
  > show "hello " "world"
data/lib/gloo.rb CHANGED
@@ -9,13 +9,27 @@ path = File.dirname( File.absolute_path( __FILE__ ) )
9
9
  require File.join( path, 'dependencies.rb' )
10
10
 
11
11
  module Gloo
12
- def self.run
13
- params = []
14
- ( params << '--cli' ) if ARGV.count.zero?
15
12
 
13
+ #
14
+ # Run gloo as the command, with the command line's arguments.
15
+ # This is the only place that reads ARGV; an engine created
16
+ # in code uses only the params it's given.
17
+ #
18
+ def self.run( argv = ARGV )
16
19
  platform = Gloo::App::Platform.new
17
20
  context = Gloo::App::EngineContext.new(
18
- params, platform, Gloo::App::Log, nil )
21
+ params_for( argv ), platform, Gloo::App::Log, nil )
19
22
  Gloo::App::Engine.new( context ).start
20
23
  end
24
+
25
+ #
26
+ # Get the engine params for the given command line arguments.
27
+ # With no arguments, gloo runs in CLI mode.
28
+ #
29
+ def self.params_for( argv )
30
+ params = argv.dup
31
+ ( params << '--cli' ) if params.empty?
32
+ return params
33
+ end
34
+
21
35
  end
data/lib/run.rb CHANGED
@@ -5,14 +5,8 @@
5
5
  # From the /lib/ directory: ruby run.rb
6
6
  #
7
7
 
8
- # Look for all required dependencies.
8
+ # Load gloo from source, then run it with the command line's arguments.
9
9
  path = File.dirname( File.absolute_path( __FILE__ ) )
10
- require File.join( path, 'dependencies.rb' )
10
+ require File.join( path, 'gloo.rb' )
11
11
 
12
- params = []
13
- ( params << '--cli' ) if ARGV.count.zero?
14
-
15
- platform = Gloo::App::Platform.new
16
- context = Gloo::App::EngineContext.new(
17
- params, platform, Gloo::App::Log, nil )
18
- Gloo::App::Engine.new( context ).start
12
+ Gloo.run
@@ -0,0 +1,31 @@
1
+ #
2
+ # Embedded gloo tests.
3
+ # An engine created in code by another Ruby program uses only the
4
+ # params it's given, never that program's command line (ARGV).
5
+ # The engine running these tests was started as the command, so the
6
+ # check runs a separate Ruby program (a fixture) with [system].
7
+ #
8
+
9
+ tests.app.embed [can] :
10
+
11
+ # The harness, beside this file; found with source_dir.
12
+ harness [string] : /fixtures/embed_harness.rb
13
+ host_arg [string] : host_arg.gloo
14
+ no_files [string] : 0
15
+ where [string] :
16
+
17
+ run_harness [system] :
18
+ command [string] :
19
+ get_output [bool] : true
20
+ result [string] :
21
+
22
+ ignores_host_argv [test] :
23
+ description [string] : An engine created in code ignores the host program's command line.
24
+ on_test [script] :
25
+ check ^^ for source_dir
26
+ put it into ^^.where
27
+ # Paths are quoted in case the folder has spaces.
28
+ put "ruby '" ^^.where ^^.harness "' " ^^.host_arg into ^^.run_harness.command
29
+ tell ^^.run_harness to run
30
+ eval ^^.run_harness.result = ^^.no_files
31
+ assert "expected the embedded engine to be given no files"
@@ -0,0 +1,15 @@
1
+ #
2
+ # A Ruby program that creates a gloo engine in code, as an embedding
3
+ # app would, and prints how many files the engine was given.
4
+ #
5
+ # Run it with words on its own command line: the engine must not
6
+ # take them as files, so this should always print 0. The engine is
7
+ # never started, so nothing is loaded or saved even if it does.
8
+ #
9
+ # Requires this repo's lib/gloo by path, so it checks the source,
10
+ # not an installed gloo gem.
11
+ #
12
+ require_relative '../../../lib/gloo'
13
+
14
+ context = Gloo::App::EngineContext.new( [ '--quiet' ] )
15
+ print Gloo::App::Engine.new( context ).args.files.count
@@ -20,6 +20,27 @@ tests.ctrl.each [can] :
20
20
  eval ^^.count + 1
21
21
  put it into ^^.count
22
22
 
23
+ seen [string] :
24
+ lines [text] : BEGIN
25
+ one
26
+
27
+ two
28
+ END
29
+
30
+ lns [each] :
31
+ line [string] :
32
+ in [alias] : tests.ctrl.each.lines
33
+ do [script] :
34
+ put tests.ctrl.each.seen + '[' + ^.line + ']' into tests.ctrl.each.seen
35
+
36
+ each_line_without_line_endings [test] :
37
+ description [string] : each line gives each line without its line ending; a blank line is empty.
38
+ on_test [script] :
39
+ put '' into ^^.seen
40
+ tell ^^.lns to run
41
+ eval ^^.seen = '[one][][two]'
42
+ assert "expected each line without its line ending"
43
+
23
44
  each_word [test] :
24
45
  description [string] : Count words with each word
25
46
  on_test [script] :
@@ -137,3 +158,54 @@ tests.ctrl.each_folder [can] :
137
158
  eval ^^.count = 2
138
159
  assert "expected a and a/deep"
139
160
  tell ^^.remove_tree to run
161
+
162
+ #
163
+ # Each child with group_by: on_group_start sees the group's first
164
+ # child and on_group_end its last. Each start and end logs the id of
165
+ # the child it sees.
166
+ #
167
+
168
+ tests.ctrl.each_group [can] :
169
+
170
+ starts [string] :
171
+ ends [string] :
172
+
173
+ items [can] :
174
+ a [can] :
175
+ id [string] : a
176
+ kind [string] : x
177
+ b [can] :
178
+ id [string] : b
179
+ kind [string] : x
180
+ c [can] :
181
+ id [string] : c
182
+ kind [string] : y
183
+ d [can] :
184
+ id [string] : d
185
+ kind [string] : y
186
+
187
+ grouped [each] :
188
+ child [alias] :
189
+ in [alias] : tests.ctrl.each_group.items
190
+ group_by [string] : kind
191
+ on_group_start [script] :
192
+ put tests.ctrl.each_group.starts + tests.ctrl.each_group.grouped.child.id into tests.ctrl.each_group.starts
193
+ on_group_end [script] :
194
+ put tests.ctrl.each_group.ends + tests.ctrl.each_group.grouped.child.id into tests.ctrl.each_group.ends
195
+ do [script] :
196
+
197
+ group_start_sees_first_child [test] :
198
+ description [string] : on_group_start sees the first child of the new group.
199
+ on_test [script] :
200
+ put '' into ^^.starts
201
+ tell ^^.grouped to run
202
+ eval ^^.starts = 'ac'
203
+ assert "expected each group to start with its first child"
204
+
205
+ group_end_sees_last_child [test] :
206
+ description [string] : on_group_end sees the last child of the group that's ending.
207
+ on_test [script] :
208
+ put '' into ^^.ends
209
+ tell ^^.grouped to run
210
+ eval ^^.ends = 'bd'
211
+ assert "expected each group to end with its last child"
@@ -23,6 +23,18 @@ tests.lang.exceptions [can] :
23
23
  kind [string] :
24
24
  location [string] :
25
25
 
26
+ # A scratch file with a mistake in it, for the location of an error in
27
+ # a loaded file.
28
+ broken_file [file] :
29
+ broken_name [string] : /gloo_exceptions_location.gloo
30
+ broken_src [text] : BEGIN
31
+ broken_location_demo [can] :
32
+ count [int : 3
33
+ END
34
+
35
+ on_load [script] :
36
+ put $.gloo_tmp ^.broken_name into ^.broken_file
37
+
26
38
  confirm_throw_fires_on_exception [test] :
27
39
  description [string] : Confirm throw fires on_exception and populates exception_data, but does not fire on_error.
28
40
  expect_errors [bool] : true
@@ -127,3 +139,13 @@ tests.lang.exceptions [can] :
127
139
  eval ^^.num = 0
128
140
  assert "expected the best guess, 0"
129
141
 
142
+ confirm_error_location_in_a_loaded_file [test] :
143
+ description [string] : an error in a file a script loads names that file and line, not the script.
144
+ expect_errors [bool] : true
145
+ on_test [script] :
146
+ tell ^^.broken_file to write ( ^^.broken_src )
147
+ load ( ^^.broken_file )
148
+ check ^^.error_data.location for ends_with? ( 'gloo_exceptions_location.gloo:2' )
149
+ assert "expected the location to be the loaded file and line"
150
+ tell broken_location_demo to unload
151
+ tell ^^.broken_file to delete
@@ -41,3 +41,30 @@ tests.objs.cipher [can] :
41
41
  tell tests.objs.cipher.b to decrypt
42
42
  eval tests.objs.cipher.b.data = ^^.plaintext
43
43
  assert "expected the second cipher to decrypt with the shared key"
44
+
45
+ # Fixed values, so the results never vary: 'hello' encrypted with key
46
+ # ('k' x 32) and iv ('i' x 16), and a wrong key ('0' x 32) whose
47
+ # garbage fails the padding check. (AES-CBC can't always tell a key is
48
+ # wrong: about 1 wrong key in 256 gives garbage with no error.)
49
+ fixed_iv [string] : aWlpaWlpaWlpaWlpaWlpaQ==
50
+ hello_encrypted [string] : aRyXOIIhJzBhh+BthfFstw==
51
+ wrong_key [string] : MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=
52
+ err_fired [bool] : false
53
+
54
+ on_error [script] :
55
+ put true into ^.err_fired
56
+
57
+ wrong_key_is_an_error [test] :
58
+ description [string] : decrypt with a wrong key that fails the padding check reports an error.
59
+ expect_errors [bool] : true
60
+ on_test [script] :
61
+ put false into ^^.err_fired
62
+ create tests.objs.cipher.w as cipher
63
+ put ^^.wrong_key into tests.objs.cipher.w.key
64
+ put ^^.fixed_iv into tests.objs.cipher.w.init_vector
65
+ put ^^.hello_encrypted into tests.objs.cipher.w.data
66
+ tell tests.objs.cipher.w to decrypt
67
+ eval it = false
68
+ assert "expected it to be false"
69
+ eval ^^.err_fired = true
70
+ assert "expected on_error to fire for the wrong key"
@@ -48,10 +48,13 @@ tests.objs.int [can] :
48
48
  eval ^^.a <= 100
49
49
  assert "expected it to be less than or equal to 100"
50
50
 
51
+ # A random value is only checked for its range: two random values
52
+ # are equal now and then, so 'not equal' would fail at random.
51
53
  tell ^^.a to randomize (10000)
52
- tell ^^.b to randomize (10000)
53
- eval ^^.a = ^^.b
54
- refute "expected them to not be equal"
54
+ eval ^^.a >= 0
55
+ assert "expected it to be greater than or equal to 0"
56
+ eval ^^.a < 10000
57
+ assert "expected it to be less than 10000"
55
58
 
56
59
  format_int [test] :
57
60
  description [string] : Format an integer
@@ -18,3 +18,69 @@ tests.verbs.break [can] :
18
18
  tell ^^.s to run
19
19
  eval ^^.b
20
20
  assert "expected b to be true"
21
+
22
+ # Loops whose do script breaks on its second pass.
23
+ passes [int] : 0
24
+ after [int] : 0
25
+
26
+ rep [repeat] :
27
+ times [int] : 4
28
+ index [int] :
29
+ do [script] :
30
+ put tests.verbs.break.passes + 1 into tests.verbs.break.passes
31
+ if tests.verbs.break.passes = 2 then break
32
+ put tests.verbs.break.after + 1 into tests.verbs.break.after
33
+
34
+ kids [can] :
35
+ a [string] : a
36
+ b [string] : b
37
+ c [string] : c
38
+
39
+ each_kid [each] :
40
+ child [alias] :
41
+ in [alias] : tests.verbs.break.kids
42
+ do [script] :
43
+ put tests.verbs.break.passes + 1 into tests.verbs.break.passes
44
+ if tests.verbs.break.passes = 2 then break
45
+ put tests.verbs.break.after + 1 into tests.verbs.break.after
46
+
47
+ inner [repeat] :
48
+ times [int] : 3
49
+ index [int] :
50
+ do [script] :
51
+ if tests.verbs.break.inner.index = 1 then break
52
+ put tests.verbs.break.after + 1 into tests.verbs.break.after
53
+
54
+ outer [repeat] :
55
+ times [int] : 2
56
+ index [int] :
57
+ do [script] :
58
+ tell tests.verbs.break.inner to run
59
+
60
+ break_ends_a_repeat [test] :
61
+ description [string] : break in a repeat's do script ends the loop.
62
+ on_test [script] :
63
+ put 0 into ^^.passes
64
+ put 0 into ^^.after
65
+ tell ^^.rep to run
66
+ eval ^^.passes = 2
67
+ assert "expected the loop to stop on the pass that broke"
68
+ eval ^^.after = 1
69
+ assert "expected only the first pass to get past the break"
70
+
71
+ break_ends_an_each [test] :
72
+ description [string] : break in an each's do script ends the loop.
73
+ on_test [script] :
74
+ put 0 into ^^.passes
75
+ put 0 into ^^.after
76
+ tell ^^.each_kid to run
77
+ eval ^^.passes = 2
78
+ assert "expected the loop to stop on the pass that broke"
79
+
80
+ break_ends_only_the_inner_loop [test] :
81
+ description [string] : break in an inner loop ends that loop, not the outer one.
82
+ on_test [script] :
83
+ put 0 into ^^.after
84
+ tell ^^.outer to run
85
+ eval ^^.after = 2
86
+ assert "expected each of the 2 inner loops to get past its break once"