Pure Erlang implementation of 9p2000 protocol
filesystem
fs
9p2000
erlang
9p
1% SPDX-FileCopyrightText: 2026 Łukasz Niemier <~@hauleth.dev>
2%
3% SPDX-License-Identifier: Apache-2.0
4
5-module(e9p_sysfs_SUITE).
6
7-compile(export_all).
8
9-include_lib("stdlib/include/assert.hrl").
10-include_lib("common_test/include/ct.hrl").
11
12all() -> [
13 can_list_mount_content,
14 current_process_is_listed,
15 current_process_current_function,
16 system_info_atom_count,
17 applications_list,
18 application_info,
19 application_env
20 ].
21
22init_per_suite(Config) ->
23 PrivDir = ?config(priv_dir, Config),
24 Path = filename:join(PrivDir, "sysfs"),
25 Port = 19999,
26 ok = file:make_dir(Path),
27 {ok, PID} = e9p_server:start(Port, {e9p_sysfs, []}),
28 ct:pal(Path),
29 Cmd = io_lib:format("9pfs -p ~B localhost ~s",
30 [Port, Path]),
31 ct:pal(Cmd),
32 _Out = os:cmd(Cmd, #{ exception_on_failure => true }),
33 [{sysfs, PID}, {mount, Path} | Config].
34
35end_per_suite(Config) ->
36 PID = ?config(sysfs, Config),
37 Mount = ?config(mount, Config),
38 os:cmd(["umount ", Mount], #{exception_on_failure => true}),
39 erlang:exit(PID, normal),
40 Config.
41
42can_list_mount_content(Config) ->
43 Mount = ?config(mount, Config),
44 ct:pal(Mount),
45 ?assertEqual([
46 "applications",
47 "processes",
48 "system_info"
49 ], ls(Mount)).
50
51current_process_is_listed(Config) ->
52 Mount = ?config(mount, Config),
53 Path = filename:join([Mount, "processes", pid_to_list(self())]),
54 ?assertEqual([
55 "current_function",
56 "dictionary",
57 "error_handler",
58 "garbage_collection",
59 "group_leader",
60 "heap_size",
61 "initial_call",
62 "links",
63 "message_queue_len",
64 "priority",
65 "reductions",
66 "stack_size",
67 "status",
68 "total_heap_size",
69 "trap_exit"
70 ], ls(Path)).
71
72current_process_current_function(Config) ->
73 Mount = ?config(mount, Config),
74 Path = filename:join([
75 Mount,
76 "processes",
77 pid_to_list(self()),
78 "current_function"
79 ]),
80 {ok, Bin} = file:read_file(Path),
81 ?assertEqual(~"{gen,do_call,4}", Bin).
82
83system_info_atom_count(Config) ->
84 Mount = ?config(mount, Config),
85 Path = filename:join([
86 Mount,
87 "system_info",
88 "atom_count"
89 ]),
90 {ok, Bin} = file:read_file(Path),
91 ?assertEqual(erlang:system_info(atom_count), binary_to_integer(Bin)).
92
93applications_list(Config) ->
94 Mount = ?config(mount, Config),
95 Path = filename:join([
96 Mount,
97 "applications"
98 ]),
99 List = ls(Path),
100 Apps = lists:sort(lists:map(fun({AppName, _, _}) -> atom_to_list(AppName) end,
101 application:loaded_applications())),
102 ?assertEqual(Apps, List).
103
104application_info(Config) ->
105 Mount = ?config(mount, Config),
106 Path = filename:join([
107 Mount,
108 "applications",
109 "kernel"
110 ]),
111 List = ls(Path),
112 ?assertEqual([
113 "applications",
114 "description",
115 "env",
116 "id",
117 "included_applications",
118 "maxP",
119 "maxT",
120 "mod",
121 "modules",
122 "optional_applications",
123 "registered",
124 "start_phases",
125 "vsn"
126 ], List).
127
128application_env(Config) ->
129 Mount = ?config(mount, Config),
130 Path = filename:join([
131 Mount,
132 "applications",
133 "kernel",
134 "env"
135 ]),
136 {ok, [Read]} = file:consult(Path),
137 Env = application:get_all_env(kernel),
138 ?assertEqual(Read, Env).
139
140%% Helpers
141
142ls(Path) ->
143 {ok, Files} = file:list_dir(Path),
144 % Remove `.fscache` added on macOS
145 lists:sort(Files) -- [".fscache"].