source_cell: replace #define private public with named friend grants in four tests - #7949
Merged
mohanchen merged 1 commit intoSep 10, 2026
Conversation
…in four tests
`magnetism_test`, `atom_pseudo_test`, `pseudo_nc_test` and `read_pp_test` each
switched off access control for their whole translation unit in order to call a
handful of private *methods* -- the format readers and helpers the tests exist
to exercise. No private data member is involved, and none of these files touches
PARAM, so the macro had exactly one cause.
Replace it with an explicit `friend class <fixture>;` on the class under test,
following the existing precedent in source_pw/module_pwdft/dftu_base.h
(`friend class DFTUTest;`) and parameter.h (`friend class TestParameters;`).
This is the fix AGENTS.md rule 10 names, and it narrows a whole-TU override --
which also reinterprets access control inside every standard library header the
TU pulls in -- down to one named, reviewable grant per fixture.
Friendship is not inherited, and a TEST_F body lives in a class derived from the
fixture, so each fixture gains thin forwarding wrappers and the TEST_F bodies
call those. dftu_lcao_test.cpp already documents and uses this arrangement.
- Magnetism: friend MagnetismTest, 1 wrapper (judge_parallel), 2 call sites.
- Pseudopot_upf: friend AtomPseudoTest / NCPPTest / ReadPPTest; 1 + 3 + 8
wrappers for read_pseudo_upf{,201,_vwr,_blps}, set_pseudo_type, setqfnew,
trim, trimend, complete_default_{h,atom}; 36 call sites in total.
Public members and public methods the tests already used -- Pseudopot_upf's
complete_default, init_pseudo_reader, average_p, set_upf_q, set_empty_element,
print_pseudo_upf and its public data, and Magnetism's tot_mag/abs_mag/start_mag
-- are untouched and still accessed directly.
No test expectation changed: every wrapper forwards its arguments unmodified and
returns what the private method returns. Macro occurrences in these four files
go 4 -> 0; no `#undef private` is introduced anywhere, and no file whose macro
survives is touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Critsium-xy
added a commit
to Critsium-xy/abacus-develop
that referenced
this pull request
Sep 14, 2026
One was vestigial; three had a real access route already available or one line away. Each was settled by compiling without the macro -- static inspection is not reliable for this, as the discarded half of this batch shows (below). - `read_sep_test.cpp` carries the macro but touches nothing private in `sep.h`. The directives are deleted and nothing else in the file changes, as with `cal_test.cpp` and `test_hsolver.cpp` in deepmodeling#7921. - `soc_test.cpp` read `soc.p_rot[l2p1*i + n]` five times. `Soc` already has a public `rotylm(i1, i2)` returning exactly `p_rot[l2plus1_*i1 + i2]`, so those become `soc.rotylm(i, n)` / `soc.rotylm(i+1, n)`. The one use left is `EXPECT_NE(soc.p_rot, nullptr)`, a guard that `rot_ylm()` allocated at all, which has no public equivalent; that gets `friend class SocTest;` and a one-line fixture helper. - `atom_spec_test.cpp` called the private `Pseudopot_upf::read_pseudo_upf201`. `Pseudopot_upf` already grants `AtomPseudoTest`, `NCPPTest` and `ReadPPTest` from deepmodeling#7949; this adds `AtomSpecTest` beside them plus the usual forwarding wrapper. The `atom.type` and `atom.ncpp` accesses in the same file are public members of `Atom` and never needed the macro. - `sltk_grid_test.cpp` wrote `PARAM.input.test_grid = 1` only to pass it straight into `Grid LatGrid(PARAM.input.test_grid)` -- PARAM used as a local, so it becomes one. Its genuine private access is `Grid::setMemberVariables`, which gets `friend class SltkGridTest;` and a wrapper. `Grid::pbc` and `Grid::sradius2`, also read by the test, are public. Friendship is not inherited and a TEST_F body lives in a derived class, hence the wrappers -- the arrangement established in deepmodeling#7949. `memory_test.cpp` and the three `propagator_test*.cpp` were in an earlier revision of this change and are deliberately not here: the compiler showed both assumptions wrong. `memory_test` reads `Memory::name`, `class_name`, `consume` and `init_flag`, private statics reached through `::` rather than a member access; the three propagator tests read `PARAM.input`, so they are reason-(a) work, not vestigial macros. Both need their own treatment. No test expectation changed. Macro occurrences 88 -> 84 across the tree; no `#undef private` is added and no file whose macro survives is touched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Critsium-xy
added a commit
to Critsium-xy/abacus-develop
that referenced
this pull request
Sep 14, 2026
Continues the cleanup (deepmodeling#7940, deepmodeling#7949, deepmodeling#7952, deepmodeling#7953). Four macros come off, and in three of the four files nothing is granted to anyone. - `read_sep_test.cpp` is vestigial: it carries the macro but touches nothing private in `sep.h`. The directives are deleted and nothing else changes, as with `cal_test.cpp` and `test_hsolver.cpp` in deepmodeling#7921. - `soc_test.cpp` read `soc.p_rot[l2p1*i + n]` five times, while `Soc` has a public `rotylm(i1, i2)` returning exactly `p_rot[l2plus1_*i1 + i2]`. Those become `soc.rotylm(i, n)` / `soc.rotylm(i+1, n)`. The sixth use was `EXPECT_NE(soc.p_rot, nullptr)`. That assertion is dropped rather than kept alive with a friend declaration: the next line already calls `soc.rotylm(0, 0)` and checks its value, which covers both "was it allocated" and "is it correct", and the guard was `EXPECT_` rather than `ASSERT_`, so it did not even stop the dereference that follows. `soc.h` is therefore untouched by this PR. - `atom_spec_test.cpp` calls the private `Pseudopot_upf::read_pseudo_upf201`, which writes thirteen members of its object and has no stateless form. It gets `friend class AtomSpecTest;` alongside the `AtomPseudoTest`, `NCPPTest` and `ReadPPTest` grants already there from deepmodeling#7949, plus a forwarding wrapper. The `atom.type` and `atom.ncpp` accesses in the same file are public members of `Atom` and never needed the macro. - `sltk_grid_test.cpp` wrote `PARAM.input.test_grid = 1` only to pass it into `Grid LatGrid(PARAM.input.test_grid)` -- PARAM used as a local, so it becomes one. Its genuine private access is `Grid::setMemberVariables`, 117 lines setting members from a UnitCell, so that gets `friend class SltkGridTest;` and a wrapper. `Grid::pbc` and `Grid::sradius2`, also read here, are public. Friendship is not inherited and a TEST_F body lives in a derived class, hence the wrappers -- the arrangement established in deepmodeling#7949. An earlier revision also removed the macro from `memory_test.cpp` and the three `propagator_test*.cpp`. Compiling without it showed both readings wrong: `memory_test` reads `Memory::name`, `class_name`, `consume` and `init_flag`, private statics reached through `::` rather than a member access; the propagator tests read `PARAM.input`, so they are reason-(a) work. Both are left alone, per the rule this series follows: remove the macro, or leave the file untouched. Macro occurrences 88 -> 84. Production change is two `friend` declarations, one of them added to a list that already exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 14, 2026
mohanchen
pushed a commit
that referenced
this pull request
Sep 14, 2026
Continues the cleanup (#7940, #7949, #7952, #7953). Four macros come off, and in three of the four files nothing is granted to anyone. - `read_sep_test.cpp` is vestigial: it carries the macro but touches nothing private in `sep.h`. The directives are deleted and nothing else changes, as with `cal_test.cpp` and `test_hsolver.cpp` in #7921. - `soc_test.cpp` read `soc.p_rot[l2p1*i + n]` five times, while `Soc` has a public `rotylm(i1, i2)` returning exactly `p_rot[l2plus1_*i1 + i2]`. Those become `soc.rotylm(i, n)` / `soc.rotylm(i+1, n)`. The sixth use was `EXPECT_NE(soc.p_rot, nullptr)`. That assertion is dropped rather than kept alive with a friend declaration: the next line already calls `soc.rotylm(0, 0)` and checks its value, which covers both "was it allocated" and "is it correct", and the guard was `EXPECT_` rather than `ASSERT_`, so it did not even stop the dereference that follows. `soc.h` is therefore untouched by this PR. - `atom_spec_test.cpp` calls the private `Pseudopot_upf::read_pseudo_upf201`, which writes thirteen members of its object and has no stateless form. It gets `friend class AtomSpecTest;` alongside the `AtomPseudoTest`, `NCPPTest` and `ReadPPTest` grants already there from #7949, plus a forwarding wrapper. The `atom.type` and `atom.ncpp` accesses in the same file are public members of `Atom` and never needed the macro. - `sltk_grid_test.cpp` wrote `PARAM.input.test_grid = 1` only to pass it into `Grid LatGrid(PARAM.input.test_grid)` -- PARAM used as a local, so it becomes one. Its genuine private access is `Grid::setMemberVariables`, 117 lines setting members from a UnitCell, so that gets `friend class SltkGridTest;` and a wrapper. `Grid::pbc` and `Grid::sradius2`, also read here, are public. Friendship is not inherited and a TEST_F body lives in a derived class, hence the wrappers -- the arrangement established in #7949. An earlier revision also removed the macro from `memory_test.cpp` and the three `propagator_test*.cpp`. Compiling without it showed both readings wrong: `memory_test` reads `Memory::name`, `class_name`, `consume` and `init_flag`, private statics reached through `::` rather than a member access; the propagator tests read `PARAM.input`, so they are reason-(a) work. Both are left alone, per the rule this series follows: remove the macro, or leave the file untouched. Macro occurrences 88 -> 84. Production change is two `friend` declarations, one of them added to a list that already exists. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
mohanchen
pushed a commit
that referenced
this pull request
Sep 14, 2026
… plus one friend (#7965) `orb_nonlocal_lm_test.cpp` is the last file in `module_ao` carrying `#define private public`. It reaches 78 times into `Numerical_Nonlocal_Lm` from its TEST_F bodies. Most of that needs nothing from the class: ten of the members already have public accessors, so `nnl[ip].nr` becomes `nnl[ip].getNr()`, `.r_radial[ir]` becomes `.getRadial(ir)`, `.beta_k[ik]` becomes `.getBeta_k(ik)`, and so on. Six things have no public route and get `friend class NumericalNonlocalLmTest;` plus thin read-only forwarders on the fixture: the members `label`, `kcut`, `index_proj` and `rab`, and the private methods `freemem()` and `renew()`, which the FreeAndRenew test exists to exercise. Friendship is not inherited and a TEST_F body lives in a derived class, hence the forwarders -- the arrangement established in #7949. Nothing that mutates state is exposed. The r->k->r round-trip check does reach deep -- it swaps the r-space and k-space arrays of a projector, reallocates `rab`, and calls the private `get_kradial()` -- but that code lives in `NumericalNonlocalLmTest::err_r2k2r`, a member of the fixture itself, so the friend declaration covers it and it is unchanged. 28 of the 106 total accesses are inside fixture helpers like that one and needed no edit. No test expectation changed; every substitution reads the same member through the accessor that returns it. With this, `module_ao` has no `#define private public` left. Occurrences tree-wide 84 -> 83. Production change is four lines: a forward declaration and one friend. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
mohanchen
pushed a commit
that referenced
this pull request
Sep 14, 2026
…public (#7966) Continues the cleanup (#7940, #7949, #7952, #7953, and open #7963/#7964/#7965). Two class families this time, handled by the same triage: use the accessor that exists, and grant friendship only for what genuinely has no public route. Measured against the TEST_F bodies -- accesses inside fixture members need nothing, since the fixture is the friend -- the four files touch far less than their size suggests: 10, 6, 5 and 1 sites respectively. - **`pw_basis_k_test.cpp` needs nothing granted.** Its only non-public reads are `device` and `precision`, and `PW_Basis` already has `get_device()` / `get_precision()`. - **`pw_basis_test.cpp`** reads the same two through those accessors, and calls three protected routines -- `distribute_g()`, `distribute_r()` and `getstartgr()` -- which set 7, 28 and 33 members of their object and have no stateless form. Those get `friend class ::PWBasisTEST;` and three forwarders. - **`test_hsolver_pw.cpp`** has exactly one live call into protected territory, `hamiltSolvePsiK` (30 `this->`), in the NpwxLessThanNbandsDeath test; the other references to it and to `update_precondition` are commented out. It gets `friend class ::TestHSolverPW;` and one forwarder. - **`test_hsolver_sdft.cpp`** is vestigial: every `TEST_F` in it is commented out, and the `nbands` it appeared to touch is `stowf.nbands_diag`, a member of a different class. The directives are simply deleted, as with `cal_test.cpp` and `test_hsolver.cpp` in #7921. `FFT_Bundle` gains `get_device()` and `get_precision()`. The pw tests check that `PW_Basis`'s constructor propagates device and precision into its `fft_bundle`, which is a real behavioural check and not redundant, but `FFT_Bundle`'s copies were private with no accessor. `PW_Basis` already exposes exactly this pair, so this completes a parallel that was half-present rather than inventing an accessor for a test. Both friended classes live in a namespace (`ModulePW`, `hsolver`) while the fixtures are at global scope, so each needs a global forward declaration and `friend class ::Fixture;` -- an unqualified `friend class Fixture;` would name a nonexistent class inside the namespace and silently grant nothing. Occurrences tree-wide 88 -> 84. Production change is three friend declarations with their forward declarations, plus the two `FFT_Bundle` accessors. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second tranche of the
#define private publiccleanup, after #7940. That PRremoved the macros whose cause was the code under test reading global
PARAM;this one removes the macros whose cause is the test calling a private method,
which needs a different fix and touches no global state at all.
Which files, and why these four
Classifying every macro region in the tree by what it actually covers gives two
root causes — (a) the production code reads global
PARAMitself, so thetest can only drive it by writing the private
PARAM.input/PARAM.sys; and(b) the test reaches into the class under test. (b) subdivides further, and
the cheapest sub-case is the one handled here:
For these four files that is the only cause. None of them touches
PARAM, sothere is no global-dependency work mixed in and no governance budget to balance
(unlike #7940, where the call-site cost had to be paid for by a paired cleanup).
magnetism_test.cpp—Magnetism::judge_parallel, 2 call sites.atom_pseudo_test.cpp—Pseudopot_upf::read_pseudo_upf201, 2 call sites.pseudo_nc_test.cpp—read_pseudo_upf201,complete_default_h,complete_default_atom, 8 call sites.read_pp_test.cpp—read_pseudo_upf,read_pseudo_upf201,read_pseudo_vwr,read_pseudo_blps,set_pseudo_type,setqfnew,trim,trimend, 26 call sites.In every case the private method is the thing the test exists to exercise — the
file headers say so (
read_pp_test.cpplistsread_pseudo_upf,set_pseudo_type,trim/trimendunder "Tested Functions", andmagnetism_test.cpplistsMagnetism::judge_parallel()).The fix
An explicit
friend class <fixture>;on the class under test, which is whatAGENTS.md rule 10 names and what this codebase already does in
source_pw/module_pwdft/dftu_base.h(friend class DFTUTest;) andsource_io/module_parameter/parameter.h(friend class TestParameters;). Thewhole production-side change is 15 lines:
plus the same for
Pseudopot_upfwith its three fixtures. No method changedvisibility, no signature changed, no member was added.
This is a real narrowing rather than a cosmetic one.
#define private publicreinterprets access control for the entire translation unit — including every
standard library header the TU pulls in, which is undefined behaviour that
surfaces at link or run time rather than as a compile error — and leaves that TU
disagreeing with the rest of the build about the layout and accessibility of
every class it sees. A
frienddeclaration grants one named class access to oneother class, is visible in the header where the class is defined, and is
reviewable.
Friendship is not inherited and a
TEST_Fbody lives in a class derived fromthe fixture, so each fixture gains thin forwarding wrappers and the
TEST_Fbodies call those.
dftu_lcao_test.cppalready documents and uses exactly thisarrangement. Every wrapper forwards its arguments unmodified and returns what the
private method returns.
What is deliberately unchanged
Public members and public methods the tests already used are untouched and still
accessed directly:
Pseudopot_upf::complete_default,init_pseudo_reader,average_p,set_upf_q,set_empty_element,print_pseudo_upfand its publicdata (
kbeta,qfunc,qfcoef,lloc,nqf, ...), and Magnetism'stot_mag/abs_mag/start_mag/compute_mag. Only the calls that actuallyneeded the macro were rerouted.
Magnetism::judge_parallelcould alternatively have been made a public static —it is a pure predicate that touches no member. That was not done: it would
enlarge the public API permanently to serve a test, where
frienddoes not.No
#undef privateis introduced anywhere, and no file whose macro this PR doesnot remove is touched — the scope rule from #7921.
Result
Verification
Linux,
cmake -B build -G Ninja -DBUILD_TESTING=ON -DENABLE_LCAO=ON -DENABLE_MPI=ON -DENABLE_OPENMP=ON:BUILD_EXIT=0).MODULE_CELL_read_pp,MODULE_CELL_pseudo_nc,MODULE_CELL_atom_pseudo,MODULE_CELL_bcast_atom_pseudo_test,MODULE_CELL_magnetism— all pass, with every assertion and expected valueunchanged.
commit (
d3722debd) built and run in a parallel worktree in the sameenvironment; both
commdirections are empty. The 29 are pre-existing andenvironment-related (
mpirun-based*_para/*_parallelwrappers, pluscubic_spline,math_sphbes,dav,bpcg,PSI_init, ...).agent_governance_check.py: 0 errors, exit 0. The access-hack ratchetreports nothing (4 removed, 0 added), and the global-dependency budget is
untouched because no
PARAM/GlobalV/GlobalCreference is added or removed.built, rather than skipped by the local feature configuration.
The one governance warning is "Documentation sync review": no INPUT parameter and
no user-visible behaviour changed — this is a test-visibility change only — so
docs/parameters.yamlanddocs/advanced/input_files/input-main.mdneed noupdate.
What is not here
The remaining 63 files split by the same taxonomy. The next cheapest group is
tests that read a private member for which a public getter already exists and
is simply not being used — those need no production change at all. A related but
distinct case has to be handled with care: where the assertion is
EXPECT_EQ(obj.get_x(), obj.x), substituting the getter makes it a tautology, sothe assertion has to be re-anchored to the known input value instead (which also
makes the test stronger). Beyond that lie tests that read private state with no
getter (a public
constobserver) and tests that write private state to builda fixture (a setter or a different construction path) — the latter is the
expensive tail and includes
klist_test.cpp,charge_mixing_test.cppandread_input_item_test.cpp.🤖 Generated with Claude Code