Skip to content

[move] tests/ modules should be treated as test-only for OTW construction without requiring #[test_only] #26993

@0xNeshi

Description

@0xNeshi

Steps to Reproduce Issue

Create a package with a test module placed in the tests/ directory (so it is compiled only in test mode) whose name matches an OTW-shaped struct, and construct that OTW manually:

// tests/my_module_tests.move
module my_pkg::my_module_tests;

public struct MY_MODULE_TESTS has drop {}

fun new_then_drop_otw() {
    let _otw = MY_MODULE_TESTS {}; // manual OTW construction
}

#[test]
fun some_test() {
    new_then_drop_otw();
}
  1. Run sui move test --build-env testnet. Confirm failure.
$ sui move test --build-env testnet
INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY Sui
BUILDING my_pkg
error[Sui E02005]: invalid one-time witness usage
  ┌─ ./sources/my_module_tests.move:7:16
  │
7 │     let _otw = MY_MODULE_TESTS {}; // manual OTW construction
  │                ^^^^^^^^^^^^^^^^^^ Invalid one-time witness construction. One-time witness types cannot be created manually, but are passed as an argument 'init'
  │
  = One-time witness types are structs with the following requirements: their name is the upper-case version of the module's name, they have no fields (or a single boolean field), they have no type parameters, and they have only the 'drop' ability.
  1. Add #[test_only] to the module (#[test_only] module my_pkg::my_module_tests;) and run sui move test --build-env testnet again.
$ sui move test --build-env testnet
INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY Sui
BUILDING my_pkg
Running Move unit tests
[ PASS    ] my_pkg::my_module_tests::some_test
Test result: OK. Total tests: 1; passed: 1; failed: 0
  1. Add #[test_only] to the new_then_drop_otw function (#[test_only] fun new_then_drop_otw()) and run sui move test --build-env testnet again.
$ sui move test --build-env testnet
INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY Sui
BUILDING my_pkg
Running Move unit tests
[ PASS    ] my_pkg::my_module_tests::some_test
Test result: OK. Total tests: 1; passed: 1; failed: 0

Expected Result

All 3 runs should compile. A module in tests/ is compiled only in test mode, so #[test_only] is semantically redundant there, and the existing OTW test-code carve-out should apply either way.

Expected the OTW manual construction to be allowed in step 1 (no #[test_only]), just as it is in steps 2 and 3.

Actual Result

Step 1 (no #[test_only]) fails to compile:

Invalid one-time witness construction. One-time witness types cannot be created
manually, but are passed as an argument 'init'

Steps 2 and 3 (with #[test_only] on module or function level) compiles fine. The carve-out is gated on the #[test]/#[test_only] attribute of the enclosing module/function (context.in_test in external-crates/move/crates/move-compiler/src/sui_mode/typing.rs, set only from attributes.is_test_or_test_only()), not on whether the code is compiled in test mode / lives in tests/. The same gate also governs the "cannot call init directly" check.

Suggested fix: treat all modules compiled in test-only mode (everything under tests/) as in_test = true, so the attribute is genuinely optional there.

System Information

  • OS: macOS 26.5.1 (build 25F80)
  • Compiler: sui 1.72.5-5445323ef25aq

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions