File size: 11,468 Bytes
ad474c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
"""The webOS: window manager, apps, filesystem wiring, and the memory map.

Each test either reads a desktop that nothing mutates (built once per class) or
builds its own, because the window manager is stateful and a shared desktop would
make the tests order-dependent.  ``d=2000`` throughout: it is the codec floor, and
the desktop renders a full 80x30 shader frame per call.
"""

import unittest

from hdc.gfx import SHADERS
from hdc.os import APPS, COLS, ROWS, Desktop, MemoryMap, boot, tour


class ReadOnlyDesktop(unittest.TestCase):
    """A started desktop shared by the tests that never mutate it."""

    @classmethod
    def setUpClass(cls):
        cls.desktop = Desktop(d=2000).start()

    def test_start_opens_the_tiled_session(self):
        desktop = Desktop(d=2000).start()
        self.assertEqual([w.app for w in desktop.windows],
                         ["terminal", "monitor", "files"])
        self.assertEqual(desktop.windows[desktop.focus].app, "terminal")

    def test_start_is_idempotent(self):
        desktop = Desktop(d=2000)
        desktop.start()
        before = len(desktop.windows)
        desktop.start()
        self.assertEqual(len(desktop.windows), before)

    def test_launching_an_open_app_raises_it_instead_of_duplicating(self):
        desktop = Desktop(d=2000).start()
        desktop.launch("files")
        desktop.launch("files")
        self.assertEqual(sum(1 for w in desktop.windows if w.app == "files"), 1)
        self.assertEqual(desktop.windows[desktop.focus].app, "files")

    def test_close_removes_the_focused_window(self):
        # a fresh desktop: closing mutates the session, and the class-level one
        # is shared with the frame test below
        desktop = Desktop(d=2000).start()
        before = len(desktop.windows)
        focused = desktop.focused()
        self.assertTrue(desktop.close())
        self.assertEqual(len(desktop.windows), before - 1)
        self.assertNotIn(focused, desktop.windows)

    def test_frame_has_the_grid_shape_and_a_menu_bar(self):
        frame = self.desktop.frame()
        self.assertEqual(len(frame), ROWS)
        for row in frame:
            self.assertLessEqual(len(row), COLS)
        text = "\n".join(frame)
        self.assertIn("webOS", frame[0])
        for key in ("terminal", "monitor", "shaders"):
            self.assertIn(key, frame[0])
        # the taskbar names the open windows
        self.assertIn("[Terminal]", frame[-1])
        self.assertGreater(self.desktop.last_frame_ms, 0.0)

    def test_memory_counts_the_filesystem_cells(self):
        """The filesystem writes into the machine's storage, not its own.

        This is the bug the coordinator fixed: two stores made every free-cell
        figure a lie, so the counts here are pinned.
        """
        desktop = self.desktop
        buckets = desktop.memory.buckets()
        self.assertIn("fs", buckets)
        self.assertEqual(buckets["fs"], len(desktop.fs.nodes))
        self.assertEqual(desktop.memory.stat()["cells"], len(desktop.kernel.storage))

    def test_memory_report_and_state_shape(self):
        report = self.desktop.memory.report()
        self.assertIn("cells", report)
        state = self.desktop.state()
        for key in ("frame", "windows", "memory", "fs", "status", "apps"):
            self.assertIn(key, state)
        self.assertEqual(len(state["frame"]), ROWS)
        self.assertIn("webOS", self.desktop.report())


class TestMemoryAllocator(unittest.TestCase):
    def setUp(self):
        self.desktop = Desktop(d=2000)
        self.map = self.desktop.memory

    def test_allocate_and_free(self):
        before = self.map.stat()["cells"]
        names = self.map.allocate("scratch", 4)
        self.assertEqual(len(names), 4)
        self.assertEqual(self.map.stat()["cells"], before + 4)
        self.assertEqual(self.map.buckets().get("mem"), 4)
        self.assertEqual(self.map.free("scratch"), 4)
        self.assertEqual(self.map.stat()["cells"], before)

    def test_free_unknown_tag_is_zero(self):
        self.assertEqual(self.map.free("never-allocated"), 0)

    def test_allocate_zero_is_empty(self):
        self.assertEqual(self.map.allocate("nothing", 0), [])


class TestTerminalApp(unittest.TestCase):
    def setUp(self):
        self.desktop = Desktop(d=2000).start()
        self.terminal = self.desktop.windows[0]
        self.desktop.focus = 0

    def test_typing_then_enter_runs_the_machine(self):
        self.desktop.text("stat")
        self.assertEqual(self.terminal.state["typing"], "stat")
        result = self.desktop.key("Enter")
        self.assertEqual(result["status"], "kernel: stat")
        buffer = "\n".join(self.terminal.state["buffer"])
        self.assertIn("cells=", buffer)
        self.assertEqual(self.terminal.state["typing"], "")

    def test_backspace_edits_the_typing_buffer(self):
        self.desktop.text("stad")
        self.desktop.key("Backspace")
        self.assertEqual(self.terminal.state["typing"], "sta")

    def test_empty_enter_does_not_run_anything(self):
        result = self.desktop.key("Enter")
        self.assertEqual(result["action"], "key")
        self.assertEqual(self.terminal.state["typing"], "")


class TestFilesApp(unittest.TestCase):
    def setUp(self):
        self.desktop = Desktop(d=2000).start()
        self.desktop.key("F2")
        self.window = self.desktop.focused()
        # entries are populated by render(), which the frame does
        self.desktop.frame()
        self.assertEqual(self.window.app, "files")

    def test_enter_opens_a_directory_and_u_goes_back_up(self):
        self.assertEqual(self.window.state["cwd"], "/home/user")
        self.desktop.key("Enter")                    # cursor starts on img/
        self.assertEqual(self.window.state["cwd"], "/home/user/img")
        self.desktop.key("u")
        self.assertEqual(self.window.state["cwd"], "/home/user")

    def test_enter_on_a_file_opens_the_editor(self):
        self.desktop.key("ArrowDown")                # img/ -> src/
        self.desktop.key("ArrowDown")                # src/ -> README.txt
        self.desktop.key("Enter")
        editor = self.desktop.focused()
        self.assertEqual(editor.app, "editor")
        self.assertTrue(editor.state["path"].endswith("README.txt"))

    def test_cursor_stays_inside_the_listing(self):
        for _ in range(20):
            self.desktop.key("ArrowDown")
        entries = self.window.state["entries"]
        self.assertLessEqual(self.window.state["cursor"], len(entries) - 1)


class TestEditorApp(unittest.TestCase):
    def test_appending_reaches_the_filesystem(self):
        desktop = Desktop(d=2000).start()
        path = "/home/user/README.txt"
        window = desktop.open_file(path)
        self.assertEqual(window.app, "editor")
        desktop.text("hello line")
        result = desktop.key("Enter")
        self.assertEqual(result["status"], "appended to " + path)
        self.assertIn("hello line", desktop.fs.read(path))
        self.assertEqual(window.state["typing"], "")

    def test_editor_without_a_file_says_so(self):
        desktop = Desktop(d=2000).start()
        window = desktop.launch("editor")
        desktop.frame()
        app = window.state["app"]
        self.assertIn("no file open", "\n".join(app.render(desktop, window)))


class TestShaderApp(unittest.TestCase):
    def setUp(self):
        self.desktop = Desktop(d=2000).start()
        self.desktop.key("F5")
        self.window = self.desktop.focused()
        self.desktop.frame()                         # render() picks the default

    def test_space_cycles_the_shader(self):
        first = self.window.state["shader"]
        self.assertIn(first, SHADERS)
        self.desktop.key(" ")
        second = self.window.state["shader"]
        self.assertIn(second, SHADERS)
        self.assertNotEqual(first, second)

    def test_e_toggles_the_edge_pass(self):
        self.assertFalse(self.window.state.get("edges"))
        self.desktop.key("e")
        self.assertEqual(self.window.state["edges"], "proxy")
        self.desktop.key("e")
        self.assertFalse(self.window.state.get("edges"))

    def test_the_window_holds_a_rendered_surface(self):
        surface = self.window.state["frame"]
        self.assertEqual(len(surface.rows()), self.window.inner_h - 2)
        self.assertEqual(len(surface.rows()[0]) <= self.window.inner_w, True)


class TestKeyboardRouting(unittest.TestCase):
    def test_function_keys_launch_every_app(self):
        desktop = Desktop(d=2000)
        for key, app in Desktop.FUNCTION_KEYS.items():
            result = desktop.key(key)
            self.assertEqual(result["action"], "launch")
            self.assertIsNotNone(desktop.find(app), msg=app)
        self.assertEqual(len(desktop.windows), len(APPS))

    def test_tab_cycles_focus(self):
        desktop = Desktop(d=2000).start()
        desktop.focus = 0
        desktop.key("Tab")
        self.assertEqual(desktop.focus, 1)
        desktop.key("Ctrl+Tab")
        self.assertEqual(desktop.focus, 0)

    def test_ctrl_w_closes_and_ctrl_right_moves(self):
        desktop = Desktop(d=2000).start()
        before = len(desktop.windows)
        desktop.key("Ctrl+W")
        self.assertEqual(len(desktop.windows), before - 1)

        desktop = Desktop(d=2000).start()
        window = desktop.focused()
        start_x = window.x
        result = desktop.key("Ctrl+Right")
        self.assertEqual(result["action"], "move")
        self.assertEqual(window.x, start_x + 3)

    def test_key_with_no_window_is_not_an_exception(self):
        desktop = Desktop(d=2000)
        self.assertEqual(desktop.key("q")["action"], "none")
        self.assertEqual(desktop.text("x")["action"], "none")

    def test_unknown_app_raises(self):
        with self.assertRaises(KeyError):
            Desktop(d=2000).launch("spreadsheet")


class TestClickFocus(unittest.TestCase):
    def test_click_focuses_the_window_under_the_cell(self):
        desktop = Desktop(d=2000)
        terminal = desktop.launch("terminal", 1, 1)
        monitor = desktop.launch("monitor", 50, 1)
        self.assertEqual(desktop.focused(), monitor)

        desktop.click(2, 2)
        self.assertEqual(desktop.focused(), terminal)

        desktop.click(60, 2)
        self.assertEqual(desktop.focused(), monitor)

    def test_click_on_empty_desktop_keeps_focus(self):
        desktop = Desktop(d=2000).start()
        focused = desktop.focused()
        self.assertEqual(desktop.click(0, ROWS - 1), "")
        self.assertEqual(desktop.focused(), focused)


class TestSession(unittest.TestCase):
    def test_boot_returns_a_started_desktop(self):
        desktop = boot(2000)
        self.assertEqual(len(desktop.windows), 3)
        self.assertEqual(desktop.windows[0].app, "terminal")

    def test_tour_prints_a_frame_and_the_report(self):
        text = tour(2000)
        self.assertIn("=== frame after tour ===", text)
        self.assertIn("webOS:", text)
        self.assertIn("memory:", text)


class TestMemoryMapStandalone(unittest.TestCase):
    def test_buckets_group_by_prefix(self):
        desktop = Desktop(d=2000)
        map_ = MemoryMap(desktop.kernel.storage)
        desktop.kernel.storage.write("code/0", 0)
        buckets = map_.buckets()
        self.assertIn("code", buckets)
        self.assertIn("fs", buckets)
        self.assertEqual(buckets["code"], 1)


if __name__ == "__main__":
    unittest.main()