gloo 7.0.3 → 7.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/docs/application.md +14 -2
  4. data/docs/development.md +1 -1
  5. data/docs/language_objects.md +37 -0
  6. data/docs/language_syntax.md +25 -0
  7. data/docs/objects.md +1 -1
  8. data/docs/plugins.md +9 -3
  9. data/docs/testing.md +206 -0
  10. data/docs/verbs.md +13 -0
  11. data/lib/VERSION +1 -1
  12. data/lib/VERSION_NOTES +15 -2
  13. data/lib/gloo/app/engine.rb +19 -9
  14. data/lib/gloo/app/settings.rb +20 -3
  15. data/lib/gloo/core/gloo_system.rb +7 -0
  16. data/lib/gloo/core/obj.rb +22 -1
  17. data/lib/gloo/core/tokens.rb +20 -0
  18. data/lib/gloo/core/verb.rb +20 -0
  19. data/lib/gloo/objs/basic/string_msgs.rb +6 -7
  20. data/lib/gloo/objs/basic/untyped.rb +2 -1
  21. data/lib/gloo/objs/system/file_handle.rb +5 -2
  22. data/lib/gloo/objs/web/json.rb +7 -5
  23. data/lib/gloo/persist/file_loader.rb +10 -0
  24. data/lib/gloo/persist/file_storage.rb +8 -2
  25. data/lib/gloo/persist/persist_man.rb +28 -0
  26. data/lib/gloo/verbs/context.rb +10 -2
  27. data/lib/gloo/verbs/exists.rb +14 -2
  28. data/lib/gloo/verbs/load.rb +16 -2
  29. data/lib/gloo/verbs/save.rb +14 -3
  30. data/test.gloo/ctrl/each.test.gloo +21 -7
  31. data/test.gloo/lang/declaration.test.gloo +7 -2
  32. data/test.gloo/lang/gloo_sys.test.gloo +12 -0
  33. data/test.gloo/lang/naming.test.gloo +7 -2
  34. data/test.gloo/objs/file.test.gloo +18 -2
  35. data/test.gloo/objs/fixtures/source_dir_fixture.gloo +6 -0
  36. data/test.gloo/objs/json.test.gloo +2 -0
  37. data/test.gloo/objs/obj.test.gloo +33 -0
  38. data/test.gloo/objs/string.test.gloo +2 -2
  39. data/test.gloo/verbs/context.test.gloo +12 -0
  40. data/test.gloo/verbs/exists.test.gloo +19 -0
  41. data/test.gloo/verbs/load.test.gloo +101 -2
  42. data/test.gloo/verbs/reload.test.gloo +9 -4
  43. data/test.gloo/verbs/save.test.gloo +41 -16
  44. metadata +5 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8c6f18a171f22ad2efb9354555f867568cc64baa563a09cad5909d3074eb55c
4
- data.tar.gz: 69d44646d15c1054ea6693ae2ad8cd9a10513ce258ca83703a5af0120da9ed97
3
+ metadata.gz: 211368f27a7bbea86aed449d545c28f05a62857e92e8a3942394261a336e597b
4
+ data.tar.gz: 98ea31342225ac55d1ae499727fe2e71bf1679a8c4f1523651e57ac50829a714
5
5
  SHA512:
6
- metadata.gz: 0b8c7c819ae9660f35bf1e8f1a6c93be762d8c94d1aef8a05828d0d57fa40d6415e567d9e9ef0bbf630e14cda7c6546616ed8a0c99e5d6b9d0a05daf6d1ddf29
7
- data.tar.gz: 59f620d96fe5c1cf35b3e461c5dcd690bffd069b94da9e742927b8e40d934bbcfc9dc4b23c9a1d7018d67b67f3c7212cb39244c80d95c618b4bb1f8037e9e9ef
6
+ metadata.gz: c2457159e21684fbdb72367753c52204de25393f74ee715622ad9879a6b228477f580440e516fee5c0d47ea6c198f8568bd109912abb2cbb01704e1e84d94d79
7
+ data.tar.gz: 0ba0bd0264f57f0334312c897a07b3023bd2019e835c8ec88e6e9bd827aa6af80c4a7610c1f1198958bb8948da8e2ed4981c56b725b371086df2e8950661ab07
data/README.md CHANGED
@@ -47,7 +47,8 @@ Read in order for a guided tour of the language, top to bottom, or jump straight
47
47
  8. [Operators](docs/operators.md) — math and comparison operators
48
48
  9. [Iterators](docs/iterators.md) — each, repeat
49
49
  10. [Plugins](docs/plugins.md) — core libraries and user extensions
50
- 11. [A Trivial Web App](docs/web_app.md) — a two-page "Hello World" built with the `gloo-web` core library
50
+ 11. [Testing](docs/testing.md) — writing and running gloo tests with `gloo-test`
51
+ 12. [A Trivial Web App](docs/web_app.md) — a two-page "Hello World" built with the `gloo-web` core library
51
52
 
52
53
  Working on gloo itself? See [Development](docs/development.md) — source layout, running the tests, and conventions.
53
54
 
data/docs/application.md CHANGED
@@ -17,6 +17,7 @@ Gloo keeps its configuration, projects, and logs under a per-user home directory
17
17
  - `gloo.gloo_config` — the configuration directory (see Configuration, below)
18
18
  - `gloo.gloo_projects` — the projects directory (same as `gloo.app` when an app is running)
19
19
  - `gloo.gloo_log` — the logging directory (see Logging, below)
20
+ - `gloo.gloo_tmp` — a folder for temporary files, such as scratch files a script or test writes and then deletes. It's `tmp` under the gloo home directory unless `tmp_path` in `gloo.yml` says otherwise (see Configuration, below), and it's created if it isn't there. Use it instead of `/tmp`, which doesn't exist on Windows.
20
21
 
21
22
  ```
22
23
  > show gloo.gloo_home
@@ -70,6 +71,11 @@ When specifying a file there are a couple ways to reference the gloo file to ope
70
71
  - Run the script in the file parameter and then quit.
71
72
  - If a file is provided as a parameter this option does not need to be specified.
72
73
  --test - Run in Test mode
74
+ - Runs the *.test.gloo files in the folders and files given
75
+ (the current folder if none), using the gloo-test library.
76
+ - Exits with status 1 if any test failed, or errors or warnings
77
+ were logged that no test expected; 0 otherwise.
78
+ - See Testing.
73
79
  --version - Show application version
74
80
  --help - Show the help screen
75
81
  ```
@@ -90,6 +96,12 @@ gloo:
90
96
  #
91
97
  project_path: /Users/my_user/gloo/projects
92
98
 
99
+ #
100
+ # Folder for temporary files, such as scratch files written by
101
+ # tests. Scripts get it with $.gloo_tmp.
102
+ #
103
+ tmp_path: /Users/my_user/gloo/tmp
104
+
93
105
  #
94
106
  # Run this script when starting up gloo.
95
107
  # (Only if a script file is not specified.)
@@ -148,7 +160,7 @@ Debug messages are written to the log only, but other messages are also written
148
160
 
149
161
  Only error and warning level messages are written to the error log.
150
162
 
151
- In test mode (`--test`), errors and warnings go to the log files only, not the console, so the test output is just the test results. An error a test expects (or one from a test file that deliberately contains a problem) is still in `error.log`.
163
+ In test mode (`--test`), errors and warnings go to the log files only, not the console, so the test output is just the test results. An error a test expects (or one from a test file that deliberately contains a problem) is still in `error.log`. The summary at the end of a test run says how many errors and warnings were logged, and by which tests, leaving out those a test expects (`expect_errors`). See Testing.
152
164
 
153
165
  The application logs folder is in the gloo folder. When gloo is run, the log files will be created if they do not exist. To trim the logs, just delete those log files.
154
166
 
@@ -199,4 +211,4 @@ help> quit
199
211
 
200
212
  ### Narrative Docs in the Shell
201
213
 
202
- This page and its ten siblings (`docs/*.md` in the interpreter's source) are the same files shown by `help> doc {name}` — there's no separate doc server or web mode. A `gloo --doc` local web server was considered and put off in favor of this: one system, no separate build/serve step.
214
+ This page and the others in `docs/` (in the interpreter's source) are the same files shown by `help> doc {name}` — there's no separate doc server or web mode. A `gloo --doc` local web server was considered and put off in favor of this: one system, no separate build/serve step.
data/docs/development.md CHANGED
@@ -54,7 +54,7 @@ Integration tests written in gloo itself, using the `[test]` object from the `gl
54
54
  gem install gloo-test gloo-yaml gloo-md
55
55
  ```
56
56
 
57
- Each `*.test.gloo` file holds `[test]` objects whose `on_test` script uses `assert` and `refute`:
57
+ Each `*.test.gloo` file holds `[test]` objects whose `on_test` script uses `assert` and `refute`. How to write them (assertions, shuffled order, `expect_errors`, `expect_fail`, scratch files and fixtures) is in Testing; this section covers only what's particular to the interpreter's own suite. A test looks like this:
58
58
 
59
59
  ```gloo
60
60
  tests [can] :
@@ -4,6 +4,8 @@
4
4
 
5
5
  - Object Naming
6
6
  - Nested Container Shorthand
7
+ - Documenting an Object
8
+ - Where an Object Came From
7
9
  - Keywords
8
10
  - Literals
9
11
  - Value Conversion
@@ -106,10 +108,45 @@ floating comment in the file instead. When a name is declared in more
106
108
  than one loaded file, the first non-empty doc wins (same rule as the
107
109
  first value).
108
110
 
111
+ A doc belongs to its object: when the object is moved, extracted, or
112
+ deleted, its doc comment goes with it (see Verbs, Load & Save).
113
+
109
114
  `list` can show every documented object in a listed tree: turn on the
110
115
  `list_docs` setting (off by default) — see `list` and `settings` in the
111
116
  in-app help.
112
117
 
118
+ ## Where an Object Came From
119
+
120
+ Every object can say which file it was loaded from: `check {obj} for
121
+ source_file` puts the file's full path in `it`, and `check {obj} for
122
+ source_dir` puts that file's folder (with no trailing slash). This lets a
123
+ script reach files stored next to it without a hard-coded path. For
124
+ example, a test can load a fixture that sits beside its own file:
125
+
126
+ ```gloo
127
+ tests.my_area [can] :
128
+ where [string] :
129
+ fixture [string] : /fixtures/sample.gloo
130
+
131
+ loads_its_fixture [test] :
132
+ on_test [script] :
133
+ check ^^ for source_dir
134
+ put it into ^^.where
135
+ load ( ^^.where ^^.fixture )
136
+ ```
137
+
138
+ The answer comes from the nearest object, going up from the one asked,
139
+ that a loaded file declares. So a nested object answers with its file,
140
+ even when its root container (like `tests` above) is declared in many
141
+ files. An object created at run time inside a loaded one answers with
142
+ that one's file. When several files declare the same object (a namespace
143
+ container), the first file loaded answers.
144
+
145
+ An object that didn't come from a file (created at run time, or
146
+ unloaded) answers blank (`''`); that isn't an error. On a `[file]`
147
+ object, `source_file` is the `.gloo` file that declared it, not the path
148
+ it holds.
149
+
113
150
  ## Keywords
114
151
 
115
152
  Gloo doesn't reserve words the way many languages do. A verb keyword like `put` or an object type name like `string` can also be used as an object name — there's no parser conflict, because verbs are only looked up as the first word of a statement, and object type names are only looked up where a type is expected (inside the `[ ]` on a declaration). Everywhere else, the word is just a pathname segment (see Object Naming, above).
@@ -3,6 +3,7 @@
3
3
  **Contents**
4
4
 
5
5
  - Color
6
+ - Computed Arguments
6
7
  - Errors
7
8
  - Events
8
9
  - Function Calls
@@ -43,6 +44,28 @@ colors [can] :
43
44
 
44
45
  See also: Show.
45
46
 
47
+ ## Computed Arguments
48
+
49
+ Some verbs take literal text rather than an object or an expression: a file name, a path, a name. Each of these can instead be an expression in parentheses. Gloo works out the expression and uses its value as the text:
50
+
51
+ ```
52
+ load ( $.gloo_tmp '/scratch.gloo' )
53
+ load lib ( lib_name )
54
+ save my_obj to ( $.gloo_tmp '/my_obj' )
55
+ context ( 'app.' section )
56
+ exists? object ( type_name )
57
+ ```
58
+
59
+ This works for `load` (a file, `ext` or `lib`), `save … to`, `context` and `exists?`. Anything not in parentheses is still taken literally, so `load common/app/*` still loads those files. A computed file name goes through the same lookup as a literal one, so wildcards, the `.gloo` extension and project-relative names work the same way.
60
+
61
+ The `+` is optional when joining text, so `( $.gloo_tmp '/scratch.gloo' )` and `( $.gloo_tmp + '/scratch.gloo' )` are the same. A `[file]` or `[string]` object holding a path works too: `load ( my.file_obj )`.
62
+
63
+ If the expression can't be worked out (for example, an object it names doesn't exist), that's reported as an error and the verb doesn't run.
64
+
65
+ For anything else, `run ~>` works out a whole command and runs it: `run ~> 'load ' $.gloo_tmp '/x.gloo'`. Prefer the parentheses where they're available; a command built this way breaks if a path has spaces in it.
66
+
67
+ See also: Load, Save, Context, Exists.
68
+
46
69
  ## Errors and Warnings
47
70
 
48
71
  Gloo makes a best guess and keeps going when it can, but it never does so silently: every problem is reported, in one of these ways.
@@ -254,6 +277,7 @@ gloo.gloo_home # Get the gloo home directory
254
277
  gloo.gloo_config # Get the gloo configuration directory
255
278
  gloo.gloo_projects # Get the gloo projects directory
256
279
  gloo.gloo_log # Get the gloo logging directory
280
+ gloo.gloo_tmp # Get the gloo tmp directory, for temporary files
257
281
 
258
282
  SCREEN
259
283
  gloo.screen_lines # Get the number of lines on screen.
@@ -308,6 +332,7 @@ See also: Pathname.
308
332
 
309
333
  - **A question** (`check x for exists?`, `contains?`, `starts_with?`, …) puts its answer in `it`.
310
334
  - **A command or message that works out a value** puts that value in `it` (`eval`, `put`, `show 3 + 4`) — even if it also stores it somewhere, in the object's own value (`trim`, `sub`, `format_for_html`) or in a child (`run` on an `erb`, `http_get` or `system` object also sets its `result`).
335
+ - **A message that puts its result into a destination you name** puts its status in `it`: `true` when it worked, `false` when it didn't. With no destination, the result itself goes in `it`. So `tell f to read ( text )` puts the file's contents in `text` and `true` in `it`, while `tell f to read` puts the contents in `it`. The same goes for json `parse ( dst )` and string `split_list ( delim dst )`.
311
336
  - **A pure action** — `list`, a bare `show`, deleting, opening a file — leaves `it` alone, so a value in `it` survives it. Running a script or a loop doesn't set `it` either, though the commands inside it do.
312
337
  - **When something goes wrong** in a question or a command that would set `it`, the problem is reported and `it` is `false`. The same goes for a message that never gets to run — the object doesn't exist, or doesn't take that message — including `run` itself: `it` is `false`, so no stale value from an earlier line is left behind.
313
338
 
data/docs/objects.md CHANGED
@@ -4,7 +4,7 @@ Everything in gloo is an object. Strings, numbers, containers, scripts, function
4
4
 
5
5
  Gloo ships with a large set of built-in object types, and core libraries and extensions can add more. This page doesn't try to cover them all — it walks through three common ones to get a feel for how objects work. For the complete list of object types, and every message each one supports, use the in-app help: enter `help` (or `?`), then `objects` to list them all, or `object {name}` for detail on one (see Application, Help).
6
6
 
7
- An object's **type declares which messages it can receive** — `up` and `trim` for a `string`, `inc` for an `integer`, `run` for a `script`. An object doesn't have to have a type: with none it is `untyped` (short `any`), declared with a bare colon (`slot :`), with `[any]`, or by `create` with no `as`. An untyped object still takes the messages every object understands (`blank?`, `contains?`, `responds_to?`, `doc`, `reload`, `unload`) — just not the type-specific ones. Of those, `blank?` asks about the object's simple value only, and `contains?` asks whether it has children: a container with children but no value of its own is blank, and contains. Untyped is the right choice when you only need to hold, compare, or show a value: a generic result slot, a config value passed straight through, or a field whose kind of value changes over its life.
7
+ An object's **type declares which messages it can receive** — `up` and `trim` for a `string`, `inc` for an `integer`, `run` for a `script`. An object doesn't have to have a type: with none it is `untyped` (short `any`), declared with a bare colon (`slot :`), with `[any]`, or by `create` with no `as`. An untyped object still takes the messages every object understands (`blank?`, `contains?`, `responds_to?`, `doc`, `source_dir`, `source_file`, `reload`, `unload`) — just not the type-specific ones. Of those, `blank?` asks about the object's simple value only, and `contains?` asks whether it has children: a container with children but no value of its own is blank, and contains. Untyped is the right choice when you only need to hold, compare, or show a value: a generic result slot, a config value passed straight through, or a field whose kind of value changes over its life.
8
8
 
9
9
  **Contents**
10
10
 
data/docs/plugins.md CHANGED
@@ -29,6 +29,12 @@ settings [can] :
29
29
 
30
30
  The loader runs these top-of-file `load lib` / `load ext` lines before it builds the object tree, so declarations below them can use the library's types. Only `load lib` and `load ext` are recognized this way, and only above the first object — a `load lib` in a script still runs when that script runs, as before.
31
31
 
32
+ When working on a core library, `load lib` loads the installed gem, not the copy you're editing, so its tests quietly test the installed version. To test the edited source, run from the library's folder with `RUBYLIB` pointing at its `lib/`:
33
+
34
+ ```
35
+ RUBYLIB="$(pwd)/lib" gloo --test test/
36
+ ```
37
+
32
38
  ### Available Core Libraries
33
39
 
34
40
  - **CLI** — Use the `gloo-cli` gem when building CLI applications.
@@ -60,7 +66,7 @@ The loader runs these top-of-file `load lib` / `load ext` lines before it builds
60
66
  > load lib md
61
67
  help> object markdown
62
68
  ```
63
- - **Test** — Use the `gloo-test` gem to manually include. See Test Runner for notes about the gloo test runner.
69
+ - **Test** — Use the `gloo-test` gem to write and run gloo tests. `gloo --test` loads it for you; see Testing for how to write and run tests.
64
70
  - Library Objects: Test
65
71
  - Library Verbs: Assert, Refute
66
72
  - See also: Eval, It
@@ -348,7 +354,7 @@ class Stats < Gloo::Core::Obj
348
354
  end
349
355
  ```
350
356
 
351
- `self.messages` should call `super` and append to it — the base `Gloo::Core::Obj` already contributes messages every object receives (`reload`, `unload`, `blank?`, `contains?`, `responds_to?`).
357
+ `self.messages` should call `super` and append to it — the base `Gloo::Core::Obj` already contributes messages every object receives (`save`, `reload`, `unload`, `blank?`, `contains?`, `responds_to?`, `doc`, `source_dir`, `source_file`).
352
358
 
353
359
  Used from gloo, once loaded:
354
360
 
@@ -374,7 +380,7 @@ Add `self.doc_data` to every verb/object class (see the `beep` and `stats` examp
374
380
 
375
381
  ### Testing an extension
376
382
 
377
- Add a Ruby unit test per verb/object class under `test/` (the same conventions as the interpreter's own tests, see Development, Tests — inherit from the project's `GlooTest`/`BaseEngineTest` base), and add a `*.test.gloo` integration test that loads the extension and exercises it end to end, e.g. `extensions/beep/test/beep.test.gloo`:
383
+ Add a Ruby unit test per verb/object class under `test/` (the same conventions as the interpreter's own tests, see Development, Tests — inherit from the project's `GlooTest`/`BaseEngineTest` base), and add a `*.test.gloo` integration test that loads the extension and exercises it end to end (see Testing for how to write and run gloo tests), e.g. `extensions/beep/test/beep.test.gloo`:
378
384
 
379
385
  ```gloo
380
386
  tests [can] :
data/docs/testing.md ADDED
@@ -0,0 +1,206 @@
1
+ # Testing
2
+
3
+ How to write and run tests for gloo code: an app, an extension or a core library. Tests are written in gloo itself, in `*.test.gloo` files, using the `[test]` object and the `assert` and `refute` verbs from the `gloo-test` core library. This page is about those gloo tests. Ruby unit tests for an extension's Ruby code are covered in Plugins, Testing an Extension; for the interpreter's own tests, see Development, Tests.
4
+
5
+ **Contents**
6
+
7
+ - Running Tests
8
+ - Writing a Test
9
+ - Assertions
10
+ - Test Order and Setup
11
+ - Errors and Warnings
12
+ - Tests Meant to Fail
13
+ - Files in Tests
14
+
15
+ ## Running Tests
16
+
17
+ Install the `gloo-test` gem, then run gloo in test mode with the tests to run:
18
+
19
+ ```
20
+ gem install gloo-test
21
+ gloo --test path/to/tests/
22
+ ```
23
+
24
+ `--test` loads the `gloo-test` library itself, so a test file doesn't need `load lib test`. Give any mix of folders and single files. In a folder, every `*.test.gloo` file is run, including those in subfolders. With no path, gloo looks in the current folder.
25
+
26
+ While the tests run, gloo shows a `.` for each test that passes and an `x` for each one that fails. Then it lists the failures, each with the test's path, its description and the message of the assertion that failed, and ends with a summary:
27
+
28
+ ```
29
+ x.....
30
+
31
+ *** Failures (1) ***
32
+
33
+ tests.math.wrong_on_purpose -> A failing test, to see the output.
34
+ expected 2 + 2 to be 5
35
+
36
+ Tests: 6 • Passed: 5 • Failed: 1
37
+ Assertions: 6 • Files: 2
38
+ Tests finished in 0.01 seconds
39
+ ```
40
+
41
+ In test mode, errors and warnings go to the log files, not the console, so the output is just the test results (see Application, Logging). The summary says when any were logged; see Errors and Warnings, below.
42
+
43
+ When the run is over, gloo exits with status 0 if it passed, and 1 if any test failed or the summary reports errors or warnings that no test expected. A missing test file or folder counts too. So a script or CI job can tell a good run from a bad one:
44
+
45
+ ```
46
+ gloo --test test/ && echo "all good"
47
+ ```
48
+
49
+ ## Writing a Test
50
+
51
+ A test file holds one root container with `[test]` objects inside it. By convention, the root is `tests`, and each file adds its own area under it with nested container shorthand (see Language, Objects), so many test files can share the `tests` root:
52
+
53
+ ```gloo
54
+ tests.math [can] :
55
+
56
+ total [int] : 0
57
+
58
+ adds_up [test] :
59
+ description [string] : Adding two numbers.
60
+ on_test [script] :
61
+ eval 2 + 2
62
+ put it into ^^.total
63
+ eval ^^.total = 4
64
+ assert "expected 2 + 2 to be 4"
65
+ ```
66
+
67
+ A `[test]` has two children:
68
+
69
+ - `description` — what the test checks. It's shown with the test's path when the test fails.
70
+ - `on_test` — the script that does the work and makes the assertions.
71
+
72
+ Inside `on_test`, `^` is the test and `^^` is the container that holds it, so `^^.total` is the `total` object beside the test. Objects a test works with usually sit in the container, next to the tests.
73
+
74
+ ## Assertions
75
+
76
+ `assert` and `refute` look at `it`. `assert` passes if `it` is true; `refute` passes if `it` is false. Each takes an optional message, which is shown if it fails, so write it as what was expected:
77
+
78
+ ```gloo
79
+ check ^^.name for blank?
80
+ refute "expected the name to be set"
81
+
82
+ eval ^^.count > 0
83
+ assert "expected at least one item"
84
+ ```
85
+
86
+ `expect` is another name for `assert`, and `expect_not` for `refute`.
87
+
88
+ So a test usually puts the thing it's checking into `it` first: with `eval` for a comparison, `check` for a question (`blank?`, `contains?`, `exists?`), or a message or verb that sets `it` (see Language, Syntax, It). A test can have any number of assertions, and it fails if any one of them fails.
89
+
90
+ ## Test Order and Setup
91
+
92
+ Tests run in a random order: the files are shuffled, and so are the tests within each file. A test mustn't depend on another test having run first. Each one sets up what it needs and cleans up after itself.
93
+
94
+ Between files, the heap is emptied: objects a file loads are gone before the next file runs. Within a file, the tests share the file's objects, so a test that uses one should set it at the start rather than rely on its declared value. For example, `put false into ^^.err_fired` before the step that should set it.
95
+
96
+ Setup that's the same for every test in a file, and that no test changes, can go in an `on_load` script in the file's container. It runs when the file loads, before any of its tests:
97
+
98
+ ```gloo
99
+ tests.files [can] :
100
+
101
+ scratch [file] :
102
+
103
+ # Runs when this file loads, before any test.
104
+ on_load [script] :
105
+ put $.gloo_tmp '/my_scratch.gloo' into ^.scratch
106
+ ```
107
+
108
+ ## Errors and Warnings
109
+
110
+ The run keeps track of the errors and warnings logged by each test. If any were logged, the summary gets an extra line naming the tests that logged them:
111
+
112
+ ```
113
+ Tests: 4 • Passed: 4 • Failed: 0
114
+ Assertions: 4 • Files: 1
115
+ 1 error logged during the run; see ~/gloo/logs/error.log
116
+ tests.files.missing_file_is_an_error (1 error)
117
+ Tests finished in 0.0 seconds
118
+ ```
119
+
120
+ Problems logged while a test file loads, outside any test, are counted too, as `(outside tests: …)`. A clean run has no such line. Treat one as a problem even when every test passed: something went wrong that no assertion caught.
121
+
122
+ A test that triggers an error or a warning on purpose (to check an `on_error` handler, or that bad input is reported) sets `expect_errors` to true. What it logs is then left out of the summary:
123
+
124
+ ```gloo
125
+ tests.files [can] :
126
+
127
+ err_fired [bool] : false
128
+
129
+ on_error [script] :
130
+ put true into ^.err_fired
131
+
132
+ missing_file_is_an_error [test] :
133
+ description [string] : Loading a file that isn't there fires on_error.
134
+ expect_errors [bool] : true
135
+ on_test [script] :
136
+ put false into ^^.err_fired
137
+ load no_such_file_xyz
138
+ eval ^^.err_fired = true
139
+ assert "expected on_error to fire"
140
+ ```
141
+
142
+ ## Tests Meant to Fail
143
+
144
+ A test that's meant to fail, such as one that shows an assertion really does fail when it should, sets `expect_fail` to true. It then passes if at least one of its assertions fails, and fails if none does:
145
+
146
+ ```gloo
147
+ false_fails_an_assert [test] :
148
+ description [string] : assert fails when it is false.
149
+ expect_fail [bool] : true
150
+ on_test [script] :
151
+ eval 1 = 2
152
+ assert "this one is meant to fail"
153
+ ```
154
+
155
+ `expect_fail` can't tell which assertion failed, or why, so a test can fail for the wrong reason and still pass. Keep a test like this to the one thing it checks. That's good practice for every test, but it matters most here.
156
+
157
+ ## Files in Tests
158
+
159
+ **Scratch files.** A test that needs a file it writes and then deletes should put it in the gloo tmp folder, `$.gloo_tmp`, rather than `/tmp`, which doesn't exist on Windows (see Application, The Gloo Home Directory). Set the path once in `on_load`, as in Test Order and Setup, above. To load the file, put the path in parentheses so it's worked out rather than taken literally (see Language, Syntax, Computed Arguments). Unload what was loaded and delete the file at the end of the test:
160
+
161
+ ```gloo
162
+ tell ^^.scratch to write ( ^^.source )
163
+ load ( ^^.scratch )
164
+ # ... assertions ...
165
+ tell demo to unload
166
+ tell ^^.scratch to delete
167
+ ```
168
+
169
+ **Fixtures.** A file the tests read but never change can be kept next to the test file, for example in a `fixtures` folder. Name it something other than `*.test.gloo`, so it isn't run as a test. `check ^^ for source_dir` gives the folder of the test file (see Language, Objects, Where an Object Came From):
170
+
171
+ ```gloo
172
+ tests.files [can] :
173
+
174
+ where [string] :
175
+ fixture [string] : /fixtures/sample.gloo
176
+
177
+ loads_its_fixture [test] :
178
+ description [string] : A fixture next to this file can be loaded.
179
+ on_test [script] :
180
+ check ^^ for source_dir
181
+ put it into ^^.where
182
+ load ( ^^.where ^^.fixture )
183
+ eval sample.name = 'from the fixture'
184
+ assert "expected the fixture to load"
185
+ tell sample to unload
186
+ ```
187
+
188
+ **Broken source on purpose.** To test how gloo handles a mistake in a file, such as a missing `]` or a declaration with no name, don't put the mistake in the test file itself. It would be reported while the file loads, outside any test. Instead, keep the broken source in a `[text]` object, write it to a scratch file inside the test, and load it there. The problem then belongs to that test, which sets `expect_errors`:
189
+
190
+ ```gloo
191
+ broken_src [text] : BEGIN
192
+ broken_demo [can] :
193
+ count [int : 3
194
+ END
195
+
196
+ broken_source_still_loads [test] :
197
+ description [string] : A type missing its ] is still read as that type.
198
+ expect_errors [bool] : true
199
+ on_test [script] :
200
+ tell ^^.scratch to write ( ^^.broken_src )
201
+ load ( ^^.scratch )
202
+ eval broken_demo.count = 3
203
+ assert "expected the value 3"
204
+ tell broken_demo to unload
205
+ tell ^^.scratch to delete
206
+ ```
data/docs/verbs.md CHANGED
@@ -58,6 +58,13 @@ tell {path.to.object} to {message}
58
58
  > show it
59
59
  ```
60
60
 
61
+ `source_dir` and `source_file` are two more: they put the folder, or the full path, of the file the object was loaded from in `it` — blank if it didn't come from a file (see Language, Objects — Where an Object Came From).
62
+
63
+ ```gloo
64
+ > check my.obj for source_dir
65
+ > show it
66
+ ```
67
+
61
68
  ## Put
62
69
 
63
70
  `put` evaluates an expression and stores the result in an object.
@@ -99,6 +106,12 @@ If the name can't be resolved to a file, `load` reports `File '{name}' was not f
99
106
 
100
107
  A save is a **rewrite, not a regeneration**: comments, blank lines, and the original spacing are kept, and only the values you actually changed are re-written. A declaration you never touched comes back byte-for-byte.
101
108
 
109
+ What counts as a change is narrow: an object is changed when its value is set, or when it is created, moved (`move`, or extracted with `save … to`), or deleted (for example with `unload`); everything else is untouched and comes back exactly as written, wherever it lives. Adding, moving, or removing an object's children doesn't change the object itself: its own declaration stays as it was, and only the children's lines appear, move, or disappear.
110
+
111
+ An object's doc comment goes wherever the object goes: it moves with a moved or extracted object and disappears with a deleted one.
112
+
113
+ A save never loses or alters data: loading the saved files again gives back the same objects, with the same values and docs, under the same parents. The interpreter decides how a changed or new declaration is laid out; only what it says has to come back the same.
114
+
102
115
  `save {obj} to {path}` doesn't just write a copy — `obj` stops being declared in its old file, wherever that was. Given `app.core.settings` declared inside `app.gloo`, `save app.core.settings to config/settings` leaves `app.gloo` without a `settings` subtree and writes `config/settings.gloo` with `app.core.settings [can] : ...` at the top level (the `app`/`core` prefix is implied via nested-container shorthand, not repeated as nested declarations) plus everything `settings` owns underneath, comments included. An object with no file of its own yet (brand new, or nested under an object that's never been saved) is simply written fresh — nothing to move.
103
116
 
104
117
  If `obj`'s own subtree already has declarations spread across more than one file (the namespace-merge pattern below, applied *inside* the subtree being extracted, not just above it), extraction refuses rather than guessing which file's slice is authoritative — save each contributing file on its own first.
data/lib/VERSION CHANGED
@@ -1 +1 @@
1
- 7.0.3
1
+ 7.1
data/lib/VERSION_NOTES CHANGED
@@ -1,3 +1,17 @@
1
+ 7.1 - 2026.10.23
2
+ - Loading an empty gloo file is now allowed and only gives a warning.
3
+ - Adds configuration setting for tmp directory.
4
+ - load (file, lib, ext), save … to, context and exists? accept ( expression ) in place of a literal name or path. For example, load ( $.gloo_tmp '/x.gloo' ).
5
+ - save … to with a blank path reports the missing-path error.
6
+ - Updates for more consistent results in `it`.
7
+ - Get source file path and name for object.
8
+ - Adds doc for gloo tests.
9
+
10
+
11
+ 7.0.4 - 2026.10.19
12
+ - Documentation update.
13
+
14
+
1
15
  7.0.3 - 2026.10.15
2
16
  - Removes test dependency on gloo-beep.
3
17
 
@@ -165,7 +179,7 @@
165
179
 
166
180
 
167
181
  5.1.0 - 2026.02.15
168
- - Fixes issue with int * dec
182
+ - Fixes issue with int * dec
169
183
  - Some code cleanup
170
184
 
171
185
 
@@ -392,4 +406,3 @@ adds tag helper for apple touch icon
392
406
 
393
407
  2.0 - 2023.05.22
394
408
  - Version 2 merges gloo-lang back into a single gloo gem.
395
-
@@ -154,21 +154,31 @@ module Gloo
154
154
  @args.files.each { |f| @persist_man.load( f ) }
155
155
  end
156
156
 
157
- #
157
+ #
158
158
  # Run in gloo unit test mode.
159
+ # Then quit, with exit status 1 if the run failed (see run_tests),
160
+ # so a calling script or CI job can tell.
159
161
  #
160
162
  def run_test
161
163
  @log.debug 'Running in Test mode…'
162
-
163
- begin
164
- @lib_manager.load_lib TEST_LIB_NAME
165
- TestRunner.new( self, @args.files ).run
166
- rescue StandardError, ScriptError => ex
167
- handle_exception ex
168
- end
169
-
164
+ passed = run_tests
170
165
  @log.debug 'Tests complete.'
171
166
  quit
167
+ exit( 1 ) unless passed
168
+ end
169
+
170
+ #
171
+ # Load gloo-test and run the tests given on the command line.
172
+ # True unless the run reports that it failed, or something went
173
+ # wrong running it. An older gloo-test whose run doesn't report
174
+ # counts as passed, as it always has.
175
+ #
176
+ def run_tests
177
+ @lib_manager.load_lib TEST_LIB_NAME
178
+ return TestRunner.new( self, @args.files ).run != false
179
+ rescue StandardError, ScriptError => ex
180
+ handle_exception ex
181
+ return false
172
182
  end
173
183
 
174
184
  #
@@ -5,6 +5,7 @@
5
5
  #
6
6
 
7
7
  require 'yaml'
8
+ require 'fileutils'
8
9
 
9
10
  module Gloo
10
11
  module App
@@ -137,9 +138,6 @@ module Gloo
137
138
  @ext_path = File.join( @user_root, 'extensions' )
138
139
  Dir.mkdir( @ext_path ) unless File.exist?( @ext_path )
139
140
 
140
- @tmp_path = File.join( @user_root, 'tmp' )
141
- Dir.mkdir( @tmp_path ) unless File.exist?( @tmp_path )
142
-
143
141
  @debug_path = File.join( @user_root, 'debug' )
144
142
  Dir.mkdir( @debug_path ) unless File.exist?( @debug_path )
145
143
  end
@@ -156,6 +154,8 @@ module Gloo
156
154
  @project_path = File.join( @user_root, 'projects' )
157
155
  end
158
156
 
157
+ init_tmp_path( settings[ 'gloo' ][ 'tmp_path' ] )
158
+
159
159
  @start_with = settings[ 'gloo' ][ 'start_with' ]
160
160
  @list_indent = settings[ 'gloo' ][ 'list_indent' ]
161
161
  @list_levels = settings[ 'gloo' ][ 'list_levels' ]
@@ -167,6 +167,16 @@ module Gloo
167
167
  @theme = DEFAULT_THEME unless THEMES.include?( @theme )
168
168
  end
169
169
 
170
+ #
171
+ # Set the tmp folder: the tmp_path setting if there is one (a
172
+ # leading ~ is expanded), or tmp under the user root. Created
173
+ # if it isn't there yet.
174
+ #
175
+ def init_tmp_path( path )
176
+ @tmp_path = path.blank? ? File.join( @user_root, 'tmp' ) : File.expand_path( path.to_s )
177
+ FileUtils.mkdir_p( @tmp_path ) unless File.exist?( @tmp_path )
178
+ end
179
+
170
180
  #
171
181
  # Get the app's required directories.
172
182
  #
@@ -188,6 +198,7 @@ module Gloo
188
198
  #
189
199
  def get_default_settings
190
200
  projects = File.join( @user_root, 'projects' )
201
+ tmp = File.join( @user_root, 'tmp' )
191
202
  str = <<~TEXT
192
203
  #
193
204
  # Gloo configuration
@@ -200,6 +211,12 @@ module Gloo
200
211
  #
201
212
  project_path: #{projects}
202
213
 
214
+ #
215
+ # Folder for temporary files, such as scratch files written by
216
+ # tests. Scripts get it with $.gloo_tmp.
217
+ #
218
+ tmp_path: #{tmp}
219
+
203
220
  #
204
221
  # Run this script when starting up gloo.
205
222
  # (Only if a script file is not specified.)
@@ -179,6 +179,11 @@ module Gloo
179
179
  return @engine.settings.project_path
180
180
  end
181
181
 
182
+ # Get the Gloo tmp directory
183
+ def msg_gloo_tmp
184
+ return @engine.settings.tmp_path
185
+ end
186
+
182
187
  # The running app directory.
183
188
  def msg_app
184
189
  return @engine.settings.project_path
@@ -314,6 +319,8 @@ module Gloo
314
319
  'gloo_config — Get the gloo configuration directory.',
315
320
  'gloo_projects — Get the gloo projects directory.',
316
321
  'gloo_log — Get the gloo logging directory.',
322
+ 'gloo_tmp — Get the gloo tmp directory, for temporary files ' \
323
+ '(the tmp_path setting in gloo.yml).',
317
324
  'screen_lines — Get the number of lines on screen.',
318
325
  'screen_cols — Get the number of columns on screen.',
319
326
  'platform_cpu — Get the platform CPU.',