Skip to content

Commit c2b737a

Browse files
committed
ct_doctest: Use compile:string to compile modules
1 parent abb9180 commit c2b737a

3 files changed

Lines changed: 106 additions & 41 deletions

File tree

lib/common_test/src/ct_doctest.erl

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -278,13 +278,14 @@ if it were in a file. For example:
278278
-module(my_module).
279279
-export([foo/0]).
280280
foo() ->
281-
ok.
281+
{?MODULE, ?FUNCTION_NAME, ?LINE}.
282282
```
283283

284284
The module is then available for use in following prompts. For example:
285285

286286
```
287287
1> my_module:foo().
288+
{my_module, foo, 4}
288289
```
289290

290291
### Edge cases
@@ -752,54 +753,25 @@ run_test(Code, InitialBindings, Options) ->
752753
end
753754
end, InitialBindings, Tests),
754755
[ok];
755-
{match, [_Line_Number, _Prefix = <<"-module(">>, _Code]} ->
756-
[compile_string(Code)];
756+
{match, [_Line_Number, _Prefix = <<"-module(">>, ModContent]} ->
757+
[ModName | _] = binary:split(ModContent, [<<")">>]),
758+
[compile_string(Code, ModName)];
757759
_ ->
758760
[]
759761
end
760762
end.
761763

762-
compile_string(Code) ->
763-
764-
Toks =
765-
case erl_scan:string(unicode:characters_to_list(Code),
766-
0,
767-
[text]) of
768-
{ok, T, _} ->
769-
T;
770-
{error, {Line,Mod,Reason}, _} ->
771-
Message = io_lib:format("unknown:~p: ~ts",[Line, Mod:format_error(Reason)]),
772-
throw({error,#{ message => Message, context => Code}})
773-
end,
774-
775-
Forms = parse_tokens(Code, Toks),
776-
777-
{attribute,_,module,ModuleName} = lists:keyfind(module, 3, Forms),
778-
779-
case compile:forms(Forms, [binary, return_errors, {source, atom_to_list(ModuleName) ++ ".erl"}]) of
764+
compile_string(Code, ModName) ->
765+
FileName = unicode:characters_to_list(ModName) ++ ".erl",
766+
case compile:string(Code, [binary, return_errors, {source, FileName}]) of
780767
{ok, Module, Binary} ->
781-
{module, Module} = code:load_binary(Module, "nofile", Binary),
768+
{module, Module} = code:load_binary(Module, FileName, Binary),
782769
ok;
783770
{error, Errors, Warnings} ->
784771
Messages = [begin [{_, M}] = sys_messages:format_messages(File, "", Msgs, []), M end || {File, Msgs} <- Errors ++ Warnings],
785772
throw({error,#{ message => Messages, context => Code}})
786773
end.
787774

788-
parse_tokens(_Code, []) -> [];
789-
parse_tokens(Code, Toks) ->
790-
case lists:splitwith(fun(T) ->
791-
element(1, T) =/= dot
792-
end, Toks) of
793-
{Ts, [{dot, _} = Dot | Rest]} ->
794-
case erl_parse:parse_form(Ts ++ [Dot]) of
795-
{ok, Forms} ->
796-
[Forms | parse_tokens(Code, Rest)];
797-
{error, {Line, Mod, Reason}} ->
798-
Message = io_lib:format("unknown:~p: ~ts",[Line, Mod:format_error(Reason)]),
799-
throw({error,#{ message => Message, context => Code}})
800-
end
801-
end.
802-
803775
check_prompt_numbers(Tests) ->
804776
check_prompt_numbers(Tests, 1).
805777

lib/common_test/test/ct_doctest_SUITE.erl

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
-export([api_branches/1, module_result_modes/1,
2929
docs_filtering_and_error_formatting/1, parser_prompt_parsing/1,
3030
runtime_failure_matching/1, parse_rewrite_helpers/1, file_support/1,
31-
external_parser/1, module/1,
31+
external_parser/1, module/1, module_preprocessor/1,
3232
type_and_callback_docs/1, verbose_option/1,
3333
skipped_blocks_option/1, missing_tests_option/1,
3434
skip_tests_option/1,
@@ -47,6 +47,7 @@ all() ->
4747
file_support,
4848
external_parser,
4949
module,
50+
module_preprocessor,
5051
type_and_callback_docs,
5152
verbose_option,
5253
skipped_blocks_option,
@@ -141,12 +142,17 @@ file_support(Config) ->
141142

142143
module(_Config) ->
143144

144-
ExpectedSubstrings = ["unknown:2: unterminated atom starting with 'ok.\\n'",
145-
"unknown:2: syntax error before: ok",
146-
"test.erl:2: function f/0 undefined"],
145+
ExpectedSubstrings = ["test.erl:3:11: unterminated atom starting with 'ok.",
146+
"test.erl:3:7: syntax error before: ok",
147+
"test.erl:3:2: function f/0 undefined"],
147148

148149
expect_error_count(ct_doctest_module_mod, [], 3, ExpectedSubstrings).
149150

151+
module_preprocessor(_Config) ->
152+
%% Test that module code blocks support preprocessor features:
153+
%% ?MODULE macro, -define macros, and -record definitions.
154+
ok = ct_doctest:module(ct_doctest_module_preproc_mod).
155+
150156
external_parser(Config) ->
151157
DataDir = ?config(data_dir, Config),
152158
ParserOpt = {parser, fun ct_doctest_external_parser_mod:parse_doc/1},
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
%%
2+
%% %CopyrightBegin%
3+
%%
4+
%% SPDX-License-Identifier: Apache-2.0
5+
%%
6+
%% Copyright Ericsson AB 2026. All Rights Reserved.
7+
%%
8+
%% Licensed under the Apache License, Version 2.0 (the "License");
9+
%% you may not use this file except in compliance with the License.
10+
%% You may obtain a copy of the License at
11+
%%
12+
%% http://www.apache.org/licenses/LICENSE-2.0
13+
%%
14+
%% Unless required by applicable law or agreed to in writing, software
15+
%% distributed under the License is distributed on an "AS IS" BASIS,
16+
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
%% See the License for the specific language governing permissions and
18+
%% limitations under the License.
19+
%%
20+
%% %CopyrightEnd%
21+
%%
22+
23+
-module(ct_doctest_module_preproc_mod).
24+
-moduledoc """
25+
Test that module code blocks in doctests support preprocessor features
26+
like macros and records via epp.
27+
""".
28+
29+
-export([uses_module_macro/0, uses_define/0, uses_record/0]).
30+
31+
-doc """
32+
Module code block using ?MODULE macro:
33+
34+
```erlang
35+
-module(macro_mod).
36+
-export([name/0]).
37+
name() -> ?MODULE.
38+
```
39+
40+
```erlang
41+
1> macro_mod:name().
42+
macro_mod
43+
```
44+
""".
45+
uses_module_macro() ->
46+
ok.
47+
48+
-doc """
49+
Module code block using -define macro:
50+
51+
```erlang
52+
-module(define_mod).
53+
-export([value/0]).
54+
-define(VALUE, 42).
55+
value() -> ?VALUE.
56+
```
57+
58+
```erlang
59+
1> define_mod:value().
60+
42
61+
```
62+
""".
63+
uses_define() ->
64+
ok.
65+
66+
-doc """
67+
Module code block using -record:
68+
69+
```erlang
70+
-module(record_mod).
71+
-export([new/2, name/1, age/1]).
72+
-record(person, {name, age}).
73+
new(Name, Age) -> #person{name = Name, age = Age}.
74+
name(#person{name = N}) -> N.
75+
age(#person{age = A}) -> A.
76+
```
77+
78+
```erlang
79+
1> P = record_mod:new(alice, 30).
80+
2> record_mod:name(P).
81+
alice
82+
3> record_mod:age(P).
83+
30
84+
```
85+
""".
86+
uses_record() ->
87+
ok.

0 commit comments

Comments
 (0)