Skip to content

Relocatable OCaml - test harness#14014

Merged
OlivierNicole merged 7 commits intoocaml:trunkfrom
dra27:in-prefix-test
Jun 20, 2025
Merged

Relocatable OCaml - test harness#14014
OlivierNicole merged 7 commits intoocaml:trunkfrom
dra27:in-prefix-test

Conversation

@dra27
Copy link
Copy Markdown
Member

@dra27 dra27 commented May 4, 2025

This PR implements a test harness for Relocatable OCaml (ocaml/RFCs#53). The RFC defines "Relocatable" as meaning:

  1. The compiler binaries are identical regardless of the installation prefix (the --prefix argument passed to configure) or the working directory in which the compiler was built (the build path).
  2. The compiler binaries can be used from any disk location on the system without any further alteration to the binaries.
  3. The compiler binaries can be used from any disk location on the system without any further alteration to the user's shell environment.

Rather than building two compilers, the first property can be checked to a sufficient degree for Relocatable OCaml by searching the installed artefacts for prefix and build path. The second and third properties can be simultaneously checked by moving the compiler from prefix to a new location, and then running a series of tests to ensure that the compiler works in its new location.

Obviously, at present OCaml cannot be configured in a way which satisfies these properties, but it is of course possible to move a compiler to a new location and change the way the compiler is invoked in order to make it work. The harness in this PR implements this approach. Everything is thus tested with a combination of known failures and shims (such as setting OCAMLLIB, or explicitly invoking bytecode binaries with ocamlrun). The three further PRs which make up the proposed changes for Relocatable OCaml are based on top of this PR, and therefore update this test harness with the revised behaviour.

There are two key differences between this test harness and the main ocamltest-based testsuite:

  1. it is central to this test that it runs on the compiler having been installed to the prefix it was configured with; and
  2. the test focuses more on the operation and execution of the programs than of the actual programs themselves, requiring more control over the exact commands executed to build the tests than ocamltest offers

A consequence of the "in-prefix" part is that this is not a test that should be run by default and the fact it needs to operate outside the build tree has led to an additional harness, rather than additional features in ocamltest.

The harness itself has revealed various bugs not related to Relocatable OCaml at all (cf. ocaml/flexdll#146, #13496, #13520, #13638, #13692 and #13693, in addition to a fault in the partial linker alluded to in #13692 which will be fixed in a subsequent PR)

The tests performed are covered in testsuite/in_prefix/README.md.

In terms of review, the PR includes the first commit of #13728, which means all the changes in runtime/ can be ignored (as they all come from that commit). The next five commits alter the compiler:

  • The compilation tests are vastly simpler if they can use Ccomp.call_linker (I mean really, really, simpler - I tried!). However, in order to do that, the test harness needs to be able to control slightly more precisely the value of Config.standard_library as interpreted by Ccomp.call_linker. Having tried various approaches, the least invasive to compiler-libs seems to be to generalise Compmisc.init_path via a new Compmisc.reinit_path. There are other ways of doing this, but this one I think is the simplest that doesn't involve duplicating code from utils/ccomp.ml directly in the test harness.
  • Test_ld_conf tests the handling by both ocamlrun and ocamlc of ld.conf. This test needs to be able to read the content of the search_path variable in Dll for which I have added a Dll.search_path function.
  • Both the test harness and one of the subsequent Relocatable PRs need to iterate over the code points of a UTF-8 string, and for this reason I've added Misc.Stdlib.String.to_utf_8_seq (as always, I'm open to suggestions for a better name...), rather than having the function in the harness itself.
  • The test harness needs to be able to parse runtime-launch-info, so I've exposed Bytelink.read_runtime_launch_info and its associated types.
  • There are various parts of the tests for reproducibility which are clang-specific. The easiest way to do this seems to be to expose the ocaml_cc_vendor variable computed in configure as Config.cc_vendor, as it has a well-defined form (which I've documented in the process).

Those opening commits clearly change the compiler, and need to be reviewed as such. The last two alter the testsuite only, and I'd beseech be reviewed from a clarity perspective, rather than as nittily as the main codebase requires! In particular, I have 12 branches based on top of this all of which alter this test harness, so please bear in mind that while adjustments are of course possible, they come at a bigger cost than just editing this PR. Note also that there are a few places where behaviour is not reachable or spelled out slightly obtusely. For example, the Default_ocamlc constructor includes an ignored launch_method value, and several functions have unused parameters escaped with _. These are all altered in subsequent PRs where the reason for having this differentiation becomes (hopefully) clearer.

As far as possible, the harness learns of the configuration of the compiler statically, rather than by probing the installation. In particular, this allows errors in the installation process to be detected (e.g. if the toplevel test simply tested all the .cma files it found in libdir, it would not catch the failure to have installed one of them - hence $(OTHERLIBRARIES) is passed on the command line instead). Most things are statically placed in the Config module - BINDIR and LIBDIR are passed on the command line, because Config.bindir and Config.standard_library won't have the same meaning in subsequent PRs. Especially given the need to be able to use Ccomp.call_linker, I've not written this test to be used in a cross-compiled manner, by which I mean it can test an installation which is a cross compiler, but the test harness must itself be compiled with the same (host) configuration as the compiler it's testing.

The final commit plumbs the tests into CI - it (should be!) passing on GitHub Actions here and has also passed precheck (although I can't at present schedule a run of this precise version of the PR, as precheck seems to be down). It's also passing an even wider text matrix including Cygwin and multiple different shebang/executable/static/minimal tests in dra27#175.

The harness code itself is structured with utility functions in module Harness and information derived about the C compiler and platform in Toolchain. Module Environment (and in particular Environment.run_process) encapsulates the configuration of the configuration of the compiler installation. Cmdline and Test_in_prefix drive the entire process with TestRelocation, TestBytecodeBinaries, TestDynlink, TestLinkModes,TestTopLevel and Test_ld_conf containing the implementations of the six tests.

@dra27 dra27 added no-change-entry-needed testsuite CI: Full matrix Makes the CI test a bigger set of configurations labels May 4, 2025
@dra27 dra27 force-pushed the in-prefix-test branch from a39eb8f to f34b44c Compare May 5, 2025 16:59
@dra27 dra27 added the run-crosscompiler-tests Makes the CI run the Cross compilers test workflow label May 5, 2025
@dra27 dra27 force-pushed the in-prefix-test branch from f34b44c to 9808a1e Compare May 13, 2025 14:53
@nojb nojb requested review from OlivierNicole and nojb May 14, 2025 13:40
Copy link
Copy Markdown
Contributor

@nojb nojb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

I did not look at the test harness itself (second part of the PR).

let init_path ?(auto_include=auto_include) ?(dir="") () =
let reinit_path ?(auto_include=auto_include)
?(standard_library=Config.standard_library)
?(dir="") () =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just add the optional argument to init_path and call it a day (instead of adding reinit_path)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was nervous about adding an optional argument that was just for a test harness to a compiler-libs function which is called by user-code. Quite happy just to add ?standard_library to init_path if you think I'm being overly paranoid!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see much difference with adding the ~standard_library argument here and the existing ~dir, so yes, I would add it here and avoid adding a new function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Seq.Cons(u, to_utf_8_seq b (i + Uchar.utf_decode_length next))

let to_utf_8_seq s = to_utf_8_seq (Bytes.unsafe_of_string s) 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider adding this to the standard library :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely! My only reticence with it is that the Standard Library would want a family of these

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, clearly. Not to be done here of course!

concatenated with the bytecode. So, if the attempt with argv[0]
failed, it is worth trying again with executable_name. */
if (fd < 0 && (proc_self_exe = caml_executable_name()) != NULL) {
if (fd < 0 && proc_self_exe != NULL) {
Copy link
Copy Markdown
Contributor

@nojb nojb May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat orthogonal, but I found this comment extremely hard to understand because of its placement: in fact both the code on 485 and the one below concern the same situation, that of -custom built bytecode executables. But due to the placement of the comment in between the two chunks of code, it looks like only one of them concerns that situation. If you agree with this understand, would you mind putting both chunks of code together and moving the comment either before or after both of them?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(just for context, this part of the diff comes from #13728)

It's definitely confusing, and there's a lurking (very) minor security bug around the order in which things are done for -custom - this part of the code churns a lot in the second relocatable PR (cf. dra27@8f06c3d, which fixes the bug, but there are subsequent commits where this code churns a lot). Would it be OK to "hold that thought" for the later PR? 😊

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, of course. Thanks for the quick response.

@dra27 dra27 force-pushed the in-prefix-test branch from 9808a1e to 3924106 Compare May 15, 2025 13:39
@dra27 dra27 marked this pull request as draft May 15, 2025 13:41
@dra27
Copy link
Copy Markdown
Member Author

dra27 commented May 15, 2025

Thanks for looking over those commits, @nojb! I've marked the PR as draft just while the commit from #13728 remains

@nojb
Copy link
Copy Markdown
Contributor

nojb commented May 15, 2025

Thanks for looking over those commits, @nojb! I've marked the PR as draft just while the commit from #13728 remains

Up to you, but note that strictly speaking you do not need to wait. The reason #13728 is waiting is because it exposes the proc_self_exe value in the standard library which needs two approvals, but you could very well go ahead and merge the change here without further ado (and rebase #13728 accordingly).

@dra27 dra27 force-pushed the in-prefix-test branch from 3924106 to d39460d Compare May 15, 2025 15:17
@dra27 dra27 marked this pull request as ready for review May 15, 2025 15:17
@dra27
Copy link
Copy Markdown
Member Author

dra27 commented May 15, 2025

Up to you, but note that strictly speaking you do not need to wait.

I'd debated it, but I imagined that Damien had been more interested in checking this first commit (the plumbing) than the name in the Stdlib! It's moot now, though, as Gabriel has approved it, too.

@dra27 dra27 force-pushed the in-prefix-test branch 2 times, most recently from 0d482be to 07363c3 Compare May 20, 2025 10:13
@gasche gasche added the relocatable towards a relocatable compiler label May 20, 2025
Copy link
Copy Markdown
Contributor

@OlivierNicole OlivierNicole left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed commits 1 through 5 and only started on testsuite/in_prefix, but here is a first batch of (minor) comments.

@dra27 dra27 force-pushed the in-prefix-test branch from 07363c3 to 0c53eab Compare May 21, 2025 10:29
@dra27
Copy link
Copy Markdown
Member Author

dra27 commented May 21, 2025

Thank you both - revised version pushed (it's on the same base commit, so the "Compare" button works)

@OlivierNicole
Copy link
Copy Markdown
Contributor

Some of your typo fixes are part of the "Plumb the in-prefix tests into CI" commit, not sure that’s why you intended to do.

Copy link
Copy Markdown
Contributor

@OlivierNicole OlivierNicole left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve read through the last two commits adding the tests and plumbing them into the CI.

As suggested, I have not read with the scrupulousness that I would have taken for the compiler code, where we want to have confidence that every function has the intended behaviour. Rather, I read through the (well-written and pleasant to read!) code, trying to understand the big picture of the test harness, and to stay alert for things that look strange (it wasn’t always easy because there is really a lot of code). I didn’t find such things.

Approving. I only have minor nitpicks, most of them typos. Since you expressed that modifying this PR can be costly, feel free to fix them in another PR.

@dra27 dra27 force-pushed the in-prefix-test branch from 0c53eab to 58e0d29 Compare May 21, 2025 14:30
@dra27
Copy link
Copy Markdown
Member Author

dra27 commented May 21, 2025

Some of your typo fixes are part of the "Plumb the in-prefix tests into CI" commit, not sure that’s why you intended to do.

Indeed it wasn't!! 😱 I feel as "Mr. Fussy", and have immediately straightened my bent grass (or in this case, misplaced fixup commit). Just looking at the rest, thank you!

@dra27 dra27 force-pushed the in-prefix-test branch from 58e0d29 to 5114dbc Compare May 21, 2025 15:02
@dra27
Copy link
Copy Markdown
Member Author

dra27 commented May 21, 2025

Thank you very much, @OlivierNicole - I think I have addressed everything, apart from one question.

Since you expressed that modifying this PR can be costly, feel free to fix them in another PR.

No, this is all good - I was more meaning that wider "it might be better architected this way" moving or refactoring functions would be very painful because of changes to those functions in subsequent commits.

@dra27 dra27 force-pushed the in-prefix-test branch from 5114dbc to 534954b Compare May 21, 2025 16:44
@OlivierNicole
Copy link
Copy Markdown
Contributor

OlivierNicole commented May 21, 2025

My comments have been addressed.

dra27 added 4 commits June 10, 2025 17:57
Previously, Compmisc.init_path initialised the load path using
Config.standard_library, but this can now be altered via an optional
?standard_library argument. This is used internally when testing
compiler installations in order to allow Ccomp.call_linker to be used.
Provides a copy of the DLL search path for the test harness.
@dra27 dra27 force-pushed the in-prefix-test branch 3 times, most recently from c0affeb to 6117a96 Compare June 13, 2025 12:20
@nojb
Copy link
Copy Markdown
Contributor

nojb commented Jun 20, 2025

Anything left to do here before merging? The PR already has two approvals.

@gasche
Copy link
Copy Markdown
Member

gasche commented Jun 20, 2025

Not that I can see, I think that you or @OlivierNicole or @dra27 should go ahead and merge. (I could do it as well, but I want to encourage other maintainers to merge when the time is right.)

@OlivierNicole OlivierNicole merged commit aaf854d into ocaml:trunk Jun 20, 2025
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI: Full matrix Makes the CI test a bigger set of configurations no-change-entry-needed relocatable towards a relocatable compiler run-crosscompiler-tests Makes the CI run the Cross compilers test workflow testsuite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants