gloo 7.0.4 → 7.1.1

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/docs/application.md +16 -2
  4. data/docs/development.md +1 -1
  5. data/docs/iterators.md +24 -0
  6. data/docs/language_objects.md +34 -0
  7. data/docs/language_syntax.md +26 -1
  8. data/docs/objects.md +1 -1
  9. data/docs/plugins.md +10 -4
  10. data/docs/testing.md +206 -0
  11. data/docs/verbs.md +7 -0
  12. data/lib/VERSION +1 -1
  13. data/lib/VERSION_NOTES +18 -2
  14. data/lib/gloo/app/engine.rb +19 -9
  15. data/lib/gloo/app/log.rb +2 -2
  16. data/lib/gloo/app/settings.rb +20 -3
  17. data/lib/gloo/core/gloo_system.rb +7 -0
  18. data/lib/gloo/core/invoker.rb +20 -7
  19. data/lib/gloo/core/obj.rb +22 -1
  20. data/lib/gloo/core/tokens.rb +20 -0
  21. data/lib/gloo/core/verb.rb +31 -0
  22. data/lib/gloo/exec/exec_env.rb +6 -3
  23. data/lib/gloo/exec/script.rb +7 -0
  24. data/lib/gloo/objs/basic/script.rb +9 -0
  25. data/lib/gloo/objs/basic/string_msgs.rb +6 -7
  26. data/lib/gloo/objs/basic/untyped.rb +2 -1
  27. data/lib/gloo/objs/ctrl/each.rb +4 -2
  28. data/lib/gloo/objs/ctrl/each_child.rb +1 -1
  29. data/lib/gloo/objs/ctrl/each_dir.rb +1 -1
  30. data/lib/gloo/objs/ctrl/each_file.rb +1 -1
  31. data/lib/gloo/objs/ctrl/each_line.rb +1 -1
  32. data/lib/gloo/objs/ctrl/each_word.rb +1 -1
  33. data/lib/gloo/objs/ctrl/repeat.rb +5 -3
  34. data/lib/gloo/objs/str_utils/cipher.rb +10 -2
  35. data/lib/gloo/objs/system/file_handle.rb +5 -2
  36. data/lib/gloo/objs/web/json.rb +7 -5
  37. data/lib/gloo/persist/declaration_ledger.rb +6 -6
  38. data/lib/gloo/persist/file_loader.rb +11 -1
  39. data/lib/gloo/persist/file_storage.rb +8 -2
  40. data/lib/gloo/persist/persist_man.rb +29 -1
  41. data/lib/gloo/verbs/break.rb +7 -2
  42. data/lib/gloo/verbs/context.rb +10 -2
  43. data/lib/gloo/verbs/eval.rb +5 -4
  44. data/lib/gloo/verbs/exists.rb +14 -2
  45. data/lib/gloo/verbs/invoke.rb +7 -6
  46. data/lib/gloo/verbs/load.rb +16 -2
  47. data/lib/gloo/verbs/log.rb +8 -4
  48. data/lib/gloo/verbs/save.rb +10 -3
  49. data/lib/gloo/verbs/show.rb +15 -10
  50. data/test.gloo/ctrl/each.test.gloo +21 -7
  51. data/test.gloo/lang/declaration.test.gloo +7 -2
  52. data/test.gloo/lang/exceptions.test.gloo +22 -0
  53. data/test.gloo/lang/gloo_sys.test.gloo +12 -0
  54. data/test.gloo/lang/naming.test.gloo +7 -2
  55. data/test.gloo/objs/cipher.test.gloo +27 -0
  56. data/test.gloo/objs/file.test.gloo +18 -2
  57. data/test.gloo/objs/fixtures/source_dir_fixture.gloo +6 -0
  58. data/test.gloo/objs/int.test.gloo +6 -3
  59. data/test.gloo/objs/json.test.gloo +2 -0
  60. data/test.gloo/objs/obj.test.gloo +33 -0
  61. data/test.gloo/objs/string.test.gloo +2 -2
  62. data/test.gloo/verbs/break.test.gloo +66 -0
  63. data/test.gloo/verbs/context.test.gloo +12 -0
  64. data/test.gloo/verbs/eval.test.gloo +8 -0
  65. data/test.gloo/verbs/exists.test.gloo +19 -0
  66. data/test.gloo/verbs/invoke.test.gloo +50 -0
  67. data/test.gloo/verbs/load.test.gloo +101 -2
  68. data/test.gloo/verbs/log.test.gloo +16 -0
  69. data/test.gloo/verbs/reload.test.gloo +9 -4
  70. data/test.gloo/verbs/save.test.gloo +41 -16
  71. data/test.gloo/verbs/show.test.gloo +32 -0
  72. metadata +5 -6
@@ -191,6 +191,25 @@ module Gloo
191
191
  return @maps.select { |fs| fs.roots.include?( root ) }
192
192
  end
193
193
 
194
+ #
195
+ # The path of the file an object was loaded from, or nil if it
196
+ # didn't come from one. Walks up from the object to the first one
197
+ # a loaded file declares, so an object created at runtime under a
198
+ # loaded one gets that file. When several files declare it (a
199
+ # namespace container), the first one loaded wins.
200
+ #
201
+ def source_file_of( obj )
202
+ declared = @maps.map { |fs| [ fs, declared_by( fs ) ] }
203
+ o = obj
204
+ while o && !o.root?
205
+ match = declared.find { |_, owned| owned.key?( o ) }
206
+ return match.first.pn if match
207
+
208
+ o = o.parent
209
+ end
210
+ return nil
211
+ end
212
+
194
213
 
195
214
  #
196
215
  # Get the full path and name of the file.
@@ -265,6 +284,15 @@ module Gloo
265
284
  end
266
285
  end
267
286
 
287
+ #
288
+ # Every heap object fs's SourceDoc declares.
289
+ #
290
+ def declared_by( fs )
291
+ owned = {}.compare_by_identity
292
+ collect_owned( fs.source_doc&.children, owned )
293
+ return owned
294
+ end
295
+
268
296
  #
269
297
  # Every heap object declared by some loaded file's SourceDoc
270
298
  # other than fs.
@@ -368,7 +396,7 @@ module Gloo
368
396
  #
369
397
  def warn_if_dirty( fs )
370
398
  saver = Gloo::Persist::FileSaver.new( @engine, fs.pn, fs.obj, fs.source_doc )
371
- @engine.log.warn "#{RELOAD_DIRTY_WARNING}#{fs.pn}" if saver.dirty?
399
+ @engine.warn "#{RELOAD_DIRTY_WARNING}#{fs.pn}" if saver.dirty?
372
400
  end
373
401
 
374
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.
@@ -16,6 +16,8 @@ module Gloo
16
16
  # Run the verb.
17
17
  #
18
18
  def run
19
+ return unless take_evaluated_arg
20
+
19
21
  set_context if @tokens.token_count > 1
20
22
  show_context
21
23
  end
@@ -74,9 +76,12 @@ module Gloo
74
76
  "to that path. Use 'context root' to set the context back to " \
75
77
  'the root level. When context has been set, the pathname can ' \
76
78
  'start from the context container by use of the @. prefix.',
77
- :syntax => [ 'context {path.to.new.context}' ],
79
+ :syntax => [ 'context {path.to.new.context}', 'context ( {expression} )' ],
78
80
  :parameters => [
79
- '{path.to.new.context} — Optional. The path to the new context when setting the context.'
81
+ '{path.to.new.context} — Optional. The path to the new context when setting the context.',
82
+ '( {expression} ) — Instead of a literal path, an expression ' \
83
+ 'in parentheses: it is worked out and its value is used as ' \
84
+ 'the path to the new context.'
80
85
  ],
81
86
  :result => 'Context is optionally set. It will be set to the ' \
82
87
  'new context path when we are changing context. Context is ' \
@@ -100,6 +105,9 @@ module Gloo
100
105
  @ context.sub
101
106
  show @.msg
102
107
  @ root
108
+
109
+ # The path can be worked out:
110
+ > context ( 'context.' 'sub' )
103
111
  EXAMPLES
104
112
  }
105
113
  end
@@ -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"
@@ -23,6 +23,8 @@ module Gloo
23
23
  # Run the verb.
24
24
  #
25
25
  def run
26
+ return @engine.heap.it.set_to( false ) unless take_evaluated_arg
27
+
26
28
  if @tokens.token_count == 3
27
29
  type = @tokens.second.strip.downcase
28
30
  keyword = @tokens.last
@@ -105,7 +107,10 @@ module Gloo
105
107
  :description => 'Check to see if a verb or object type has ' \
106
108
  'been defined (or loaded), or if an object instance exists ' \
107
109
  'in the heap. With no kind, any of the three counts.',
108
- :syntax => [ 'exists? |any,object,verb,instance| {keyword}' ],
110
+ :syntax => [
111
+ 'exists? |any,object,verb,instance| {keyword}',
112
+ 'exists? |any,object,verb,instance| ( {expression} )'
113
+ ],
109
114
  :parameters => [
110
115
  "Kind of Keyword — Optional; any is the default. " \
111
116
  "any checks for a verb, an object type, or an object instance; " \
@@ -115,7 +120,10 @@ module Gloo
115
120
  "instance exists at the pathname represented by keyword. " \
116
121
  "`exists? any markdown` is identical to `exists? markdown`.",
117
122
  '{keyword} — A verb or object type keyword or keyword shortcut, ' \
118
- 'or the pathname to an object instance. Must be specified.'
123
+ 'or the pathname to an object instance. Must be specified.',
124
+ '( {expression} ) — Instead of a literal keyword, an expression ' \
125
+ 'in parentheses: it is worked out and its value is used as ' \
126
+ 'the keyword or pathname.'
119
127
  ],
120
128
  :result => 'If the verb, object type or instance exists, then ' \
121
129
  'true, otherwise false. The result will be in it.',
@@ -142,6 +150,10 @@ module Gloo
142
150
 
143
151
  \t\texists? instance exists.on_load
144
152
  \t\tshow it # true
153
+
154
+ \t\t# The keyword can be worked out
155
+ \t\texists? object ( 'mark' 'down' )
156
+ \t\tshow it # true
145
157
  EXAMPLES
146
158
  }
147
159
  end
@@ -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 ' \
@@ -23,6 +23,8 @@ module Gloo
23
23
  # Run the verb.
24
24
  #
25
25
  def run
26
+ return unless take_evaluated_arg
27
+
26
28
  if @tokens.token_count == 2
27
29
  opt = FILE_OPT
28
30
  fn = @tokens.second
@@ -109,13 +111,18 @@ module Gloo
109
111
  :syntax => [
110
112
  'load {file_name}',
111
113
  'load ext {my_ext}',
112
- 'load lib {lib_name}'
114
+ 'load lib {lib_name}',
115
+ 'load [ext|lib] ( {expression} )'
113
116
  ],
114
117
  :parameters => [
115
118
  '{file_name} — Name of the object file that is to be loaded.',
116
119
  'reference type — By default, the type is file. File can be ' \
117
120
  'specified, but does not need to be. Use ext to load a ' \
118
- 'User Extension. Use lib to load a Core Library.'
121
+ 'User Extension. Use lib to load a Core Library.',
122
+ '( {expression} ) — Instead of a literal name, an expression ' \
123
+ 'in parentheses: it is worked out and its value is used as ' \
124
+ 'the file, extension or library name. Wildcards and the ' \
125
+ '.gloo extension work as they do for a literal name.'
119
126
  ],
120
127
  :result => 'Objects are loaded into the heap. on_load scripts ' \
121
128
  'are run within the loaded objects.',
@@ -132,6 +139,9 @@ module Gloo
132
139
  'they merge in the heap. If two loaded files declare the ' \
133
140
  'same object with different values, the first one loaded ' \
134
141
  "wins and a warning is logged.\n\n" \
142
+ 'A file with no objects in it (empty, comments only, or ' \
143
+ "only 'load lib' lines) loads nothing and logs a warning " \
144
+ "naming the file; it isn't an error.\n\n" \
135
145
  'A contiguous run of whole-line comments declared ' \
136
146
  'immediately above an object (same indent, no blank line ' \
137
147
  "in between) becomes that object's doc, captured as the " \
@@ -145,6 +155,10 @@ module Gloo
145
155
  > load my/app/*
146
156
  > load ~/.my_app/settings.gloo
147
157
 
158
+ # Load a file whose name is worked out
159
+ > load ( $.gloo_tmp '/scratch.gloo' )
160
+ > load ( my.file_obj )
161
+
148
162
  # Load a User Extension
149
163
  > load ext my_ext
150
164
 
@@ -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",
@@ -48,9 +48,11 @@ module Gloo
48
48
  # Run 'save {obj} to {path}'.
49
49
  #
50
50
  def run_save_to
51
+ return unless take_evaluated_arg
52
+
51
53
  name = @tokens.before_token( TO )[ 1 ]
52
54
  path = @tokens.after_token( TO )
53
- unless path
55
+ if path.blank?
54
56
  @engine.syntax_err MISSING_PATH_ERR
55
57
  return
56
58
  end
@@ -84,13 +86,17 @@ module Gloo
84
86
  :syntax => [
85
87
  'save',
86
88
  'save {path.to.object}',
87
- 'save {path.to.object} to {path}'
89
+ 'save {path.to.object} to {path}',
90
+ 'save {path.to.object} to ( {expression} )'
88
91
  ],
89
92
  :parameters => [
90
93
  '{path.to.object} — Name of the object to save.',
91
94
  '{path} — Where to save it, relative to the project root ' \
92
95
  '(.gloo appended if omitted). Registers the mapping, so ' \
93
- 'a later bare save includes it.'
96
+ 'a later bare save includes it.',
97
+ '( {expression} ) — Instead of a literal path, an expression ' \
98
+ 'in parentheses: it is worked out and its value is used as ' \
99
+ 'the path.'
94
100
  ],
95
101
  :result => 'The file(s) are updated with the latest object ' \
96
102
  'state. This is a rewrite, not a regeneration: comments, ' \
@@ -129,6 +135,7 @@ module Gloo
129
135
  > save my_obj
130
136
  > save my_obj to sub/my_obj
131
137
  > save app.core.settings to config/settings
138
+ > save my_obj to ( $.gloo_tmp '/my_obj' )
132
139
  EXAMPLES
133
140
  }
134
141
  end
@@ -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"
@@ -38,23 +38,37 @@ tests.ctrl.each [can] :
38
38
 
39
39
  #
40
40
  # Each file / each dir over a nested folder tree.
41
- # The tree is built under /tmp and removed at the end.
41
+ # The tree is built in $.gloo_tmp and removed at the end. It's built
42
+ # and removed with shell commands, so these tests need a Unix shell.
42
43
  #
43
44
 
44
45
  tests.ctrl.each_folder [can] :
45
46
 
46
47
  count [int] : 0
48
+ walk_name [string] : /gloo_each_walk
49
+ walk [string] :
47
50
 
48
51
  make_tree [system] :
49
- command [string] : rm -rf /tmp/gloo_each_walk && mkdir -p /tmp/gloo_each_walk/a/deep /tmp/gloo_each_walk/.obs && touch /tmp/gloo_each_walk/top.md /tmp/gloo_each_walk/notes.txt /tmp/gloo_each_walk/a/x.md /tmp/gloo_each_walk/a/deep/Y.MD /tmp/gloo_each_walk/.obs/o.md
52
+ command [string] :
50
53
  get_output [bool] : false
51
54
  remove_tree [system] :
52
- command [string] : rm -rf /tmp/gloo_each_walk
55
+ command [string] :
53
56
  get_output [bool] : false
54
57
 
58
+ # The tree lives in the gloo tmp folder ($.gloo_tmp); paths are
59
+ # quoted in the commands in case that folder has spaces.
60
+ on_load [script] :
61
+ put $.gloo_tmp ^.walk_name into ^.walk
62
+ put "rm -rf '" ^.walk "' && mkdir -p '" ^.walk "/a/deep' '" ^.walk "/.obs' && cd '" ^.walk "' && touch top.md notes.txt a/x.md a/deep/Y.MD .obs/o.md" into ^.make_tree.command
63
+ put "rm -rf '" ^.walk "'" into ^.remove_tree.command
64
+ put ^.walk into ^.files.in
65
+ put ^.walk '/**/' into ^.wild.in
66
+ put ^.walk into ^.top.in
67
+ put ^.walk into ^.dirs.in
68
+
55
69
  files [each] :
56
70
  file [file] :
57
- in [file] : /tmp/gloo_each_walk
71
+ in [file] :
58
72
  ext [string] : md
59
73
  recursive [bool] : true
60
74
  do [script] :
@@ -63,7 +77,7 @@ tests.ctrl.each_folder [can] :
63
77
 
64
78
  wild [each] :
65
79
  file [file] :
66
- in [file] : /tmp/gloo_each_walk/**/
80
+ in [file] :
67
81
  ext [string] : md
68
82
  do [script] :
69
83
  eval ^^.count + 1
@@ -71,14 +85,14 @@ tests.ctrl.each_folder [can] :
71
85
 
72
86
  top [each] :
73
87
  file [file] :
74
- in [file] : /tmp/gloo_each_walk
88
+ in [file] :
75
89
  do [script] :
76
90
  eval ^^.count + 1
77
91
  put it into ^^.count
78
92
 
79
93
  dirs [each] :
80
94
  dir [file] :
81
- in [file] : /tmp/gloo_each_walk
95
+ in [file] :
82
96
  recursive [bool] : true
83
97
  do [script] :
84
98
  eval ^^.count + 1
@@ -7,7 +7,12 @@ tests.lang.declaration [can] :
7
7
  # A fixture deliberately missing the ']' after its type, loaded inside
8
8
  # its test so the syntax error it reports belongs to that test.
9
9
  # Loading reads the type as 'int' anyway.
10
- unclosed_file [file] : /tmp/gloo_declaration_test.gloo
10
+ unclosed_file [file] :
11
+
12
+ # Scratch files live in the gloo tmp folder ($.gloo_tmp).
13
+ on_load [script] :
14
+ put $.gloo_tmp '/gloo_declaration_test.gloo' into ^.unclosed_file
15
+
11
16
  unclosed_src [text] : BEGIN
12
17
  declaration_fixture [container] :
13
18
  unclosed [int : 7
@@ -20,7 +25,7 @@ END
20
25
  expect_errors [bool] : true
21
26
  on_test [script] :
22
27
  tell ^^.unclosed_file to write ( ^^.unclosed_src )
23
- load /tmp/gloo_declaration_test.gloo
28
+ load ( ^^.unclosed_file )
24
29
  eval declaration_fixture.unclosed = 7
25
30
  assert "expected the value 7"
26
31
 
@@ -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
@@ -79,6 +79,18 @@ tests.lang.gloo_sys [can] :
79
79
  eval it = ''
80
80
  refute "expected gloo_log to not be empty"
81
81
 
82
+ get_gloo_tmp [test] :
83
+ description [string] : $.gloo_tmp is a folder that exists.
84
+ f [file] :
85
+ on_test [script] :
86
+ eval $.gloo_tmp
87
+ eval it = ''
88
+ refute "expected gloo_tmp to not be empty"
89
+
90
+ put $.gloo_tmp into ^.f
91
+ check ^.f for is_dir?
92
+ assert "expected gloo_tmp to be an existing folder"
93
+
82
94
  get_screen_dims [test] :
83
95
  description [string] : Get the screen dimensions.
84
96
  i [int] :
@@ -16,7 +16,12 @@ tests.lang.naming [can] :
16
16
 
17
17
  # A fixture with a deliberately nameless object, loaded inside its
18
18
  # test so the syntax error it reports belongs to that test.
19
- nameless_file [file] : /tmp/gloo_naming_test.gloo
19
+ nameless_file [file] :
20
+
21
+ # Scratch files live in the gloo tmp folder ($.gloo_tmp).
22
+ on_load [script] :
23
+ put $.gloo_tmp '/gloo_naming_test.gloo' into ^.nameless_file
24
+
20
25
  nameless_src [text] : BEGIN
21
26
  naming_fixture [container] :
22
27
  [string] : no name given
@@ -54,7 +59,7 @@ END
54
59
  expect_errors [bool] : true
55
60
  on_test [script] :
56
61
  tell ^^.nameless_file to write ( ^^.nameless_src )
57
- load /tmp/gloo_naming_test.gloo
62
+ load ( ^^.nameless_file )
58
63
  eval naming_fixture.unnamed_1 = 'no name given'
59
64
  assert "expected the nameless object to be reached as unnamed_1"
60
65
 
@@ -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"
@@ -2,12 +2,17 @@
2
2
  # File object tests
3
3
  #
4
4
  # The file object's value is a path. These tests write a scratch
5
- # file under /tmp, exercise it, and delete it at the end.
5
+ # file in $.gloo_tmp, exercise it, and delete it at the end.
6
6
  #
7
7
 
8
8
  tests.objs.file [can] :
9
9
 
10
- f [file] : /tmp/gloo_file_test.txt
10
+ f [file] :
11
+
12
+ # Scratch files live in the gloo tmp folder ($.gloo_tmp).
13
+ on_load [script] :
14
+ put $.gloo_tmp '/gloo_file_test.txt' into ^.f
15
+
11
16
  names [file] : /Users/me/project/report.csv
12
17
 
13
18
  first [string] : first line
@@ -29,11 +34,22 @@ tests.objs.file [can] :
29
34
  refute "expected the file not to be a directory"
30
35
 
31
36
  tell ^^.f to read ( ^^.back )
37
+ eval it = true
38
+ assert "expected read into a destination to put true in it"
32
39
  eval ^^.back = 'first line'
33
40
  assert "expected read to return what was written"
34
41
 
35
42
  tell ^^.f to delete
36
43
 
44
+ read_without_a_destination_puts_the_data_in_it [test] :
45
+ description [string] : read with no destination puts the file's contents in it.
46
+ on_test [script] :
47
+ tell ^^.f to write ( ^^.first )
48
+ tell ^^.f to read
49
+ eval it = 'first line'
50
+ assert "expected the file's contents in it"
51
+ tell ^^.f to delete
52
+
37
53
  append_adds_a_line [test] :
38
54
  description [string] : append adds a new line to the file.
39
55
  on_test [script] :
@@ -0,0 +1,6 @@
1
+ #
2
+ # Fixture for obj.test.gloo: loaded from the folder next to that test
3
+ # file, found with source_dir. Not a test file itself.
4
+ #
5
+ source_dir_fixture [can] :
6
+ msg [string] : found next door
@@ -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
@@ -43,6 +43,8 @@ tests.objs.json [can] :
43
43
  description [string] : parse builds an object tree from the JSON.
44
44
  on_test [script] :
45
45
  tell ^^.payload to parse ( tests.objs.json.out )
46
+ eval it = true
47
+ assert "expected parse to put true in it"
46
48
 
47
49
  eval tests.objs.json.out.title = 'Brute Force'
48
50
  assert "expected parse to set the title"