• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 19617001874

23 Nov 2025 08:42PM UTC coverage: 80.288% (-0.002%) from 80.29%
19617001874

push

github

web-flow
[pants_ng] Next-gen options reader. (#22880)

#22862 implemented NG config. 

This PR adds support for env vars and flags, and pulls them
all together into a new `OptionsReader`.

In practice this re-uses a lot of code from the current options crate:
- The new `OptionsReader` just wraps a current `OptionParser`, but
   skips the latter's complicated bootstrapping dance.
- Env support is directly taken from the existing code.
- Flag support is reimplemented, as NG flags are handled differently.

The difference in flag handling is quite important. Currently flags
have ambiguity: given `--foo-bar-baz`, is this setting a global option
`foo_bar_baz`, or an option `bar_baz` in scope `foo`, or an option 
`baz` in scope `foo-bar`? So the existing code has to iterate
over all flags and see if they match what we're looking for.

New flags use dashes in scope names, and to separate scope
names from option names, but the latter must use underscores as word
separators. So `--foo_bar_baz`, `--foo-bar_baz` and `--foo-bar_baz`
are unambiguous. Therefore we can simply look up flags by scope and
option name in nested HashMaps.

Nothing in this PR is wired up to anything yet, so this has no impact
on end users. It is speculative work to support future NG development.

78386 of 97631 relevant lines covered (80.29%)

3.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.69
/src/python/pants/pantsd/lock_test.py
1
# Copyright 2016 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
import os
1✔
5
import shutil
1✔
6
import tempfile
1✔
7
import unittest
1✔
8
from multiprocessing import Manager, Process
1✔
9
from threading import Thread
1✔
10

11
from pants.pantsd.lock import OwnerPrintingInterProcessFileLock
1✔
12

13

14
def hold_lock_until_terminate(path, lock_held, terminate):
1✔
15
    lock = OwnerPrintingInterProcessFileLock(path)
×
16
    lock.acquire()
×
17
    lock_held.set()
×
18
    # NOTE: We shouldn't ever wait this long, this is just to ensure
19
    # we don't somehow leak child processes.
20
    terminate.wait(60)
×
21
    lock.release()
×
22
    lock_held.clear()
×
23

24

25
class TestOwnerPrintingInterProcessFileLock(unittest.TestCase):
1✔
26
    def setUp(self):
1✔
27
        self.lock_dir = tempfile.mkdtemp()
1✔
28
        self.lock_path = os.path.join(self.lock_dir, "lock")
1✔
29
        self.lock = OwnerPrintingInterProcessFileLock(self.lock_path)
1✔
30
        self.manager = Manager()
1✔
31
        self.lock_held = self.manager.Event()
1✔
32
        self.terminate = self.manager.Event()
1✔
33
        self.lock_process = Process(
1✔
34
            target=hold_lock_until_terminate, args=(self.lock_path, self.lock_held, self.terminate)
35
        )
36

37
    def tearDown(self):
1✔
38
        self.terminate.set()
1✔
39
        try:
1✔
40
            shutil.rmtree(self.lock_dir)
1✔
41
        except OSError:
×
42
            pass
×
43

44
    def test_non_blocking_attempt(self):
1✔
45
        self.lock_process.start()
1✔
46
        self.lock_held.wait()
1✔
47
        self.assertFalse(self.lock.acquire(blocking=False))
1✔
48

49
    def test_message(self):
1✔
50
        self.lock_process.start()
1✔
51
        self.lock_held.wait()
1✔
52
        self.assertTrue(os.path.exists(self.lock.message_path))
1✔
53
        with open(self.lock.message_path) as f:
1✔
54
            message_content = f.read()
1✔
55
        self.assertIn(str(self.lock_process.pid), message_content)
1✔
56

57
        os.unlink(self.lock.message_path)
1✔
58

59
        def message_fn(message):
1✔
60
            self.assertIn(self.lock.missing_message_output, message)
1✔
61

62
        self.lock.acquire(blocking=False, message_fn=message_fn)
1✔
63

64
    def test_blocking(self):
1✔
65
        self.lock_process.start()
1✔
66
        self.lock_held.wait()
1✔
67
        self.assertFalse(self.lock.acquire(timeout=0.1))
1✔
68

69
        acquire_is_blocking = self.manager.Event()
1✔
70

71
        def terminate_subproc(terminate, acquire_is_blocking):
1✔
72
            acquire_is_blocking.wait()
1✔
73
            terminate.set()
1✔
74

75
        Thread(target=terminate_subproc, args=(self.terminate, acquire_is_blocking)).start()
1✔
76

77
        def message_fn(message):
1✔
78
            self.assertIn(str(self.lock_process.pid), message)
1✔
79
            acquire_is_blocking.set()
1✔
80

81
        # NOTE: We shouldn't ever wait this long (locally this runs in ~milliseconds)
82
        # but sometimes CI containers are extremely slow, so we choose a very large
83
        # value just in case.
84
        self.assertTrue(self.lock.acquire(timeout=30, message_fn=message_fn))
1✔
85

86
    def test_reentrant(self):
1✔
87
        self.assertTrue(self.lock.acquire())
1✔
88
        self.assertTrue(self.lock.acquire())
1✔
89

90
    def test_release(self):
1✔
91
        self.assertTrue(self.lock.acquire())
1✔
92
        self.assertTrue(self.lock.acquired)
1✔
93
        self.lock.release()
1✔
94
        self.assertFalse(self.lock.acquired)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc