aboutsummaryrefslogtreecommitdiff
path: root/tests/installer-steps/test_select_locale.py
blob: b9b9ce74cea14920e49cf95c505a013c8d9226b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
"""Tests for select_locale in the archsetup installer.

select_locale resolves the system locale during pre-flight. Three inputs, in
precedence order: an existing LANG= in locale.conf wins outright, then LOCALE
from the config file, then a numbered menu.

That menu is why these tests exist. It carries its own "[1]" default, LOCALE is
optional in archsetup.conf.example, and archsetup does not require an archangel
install -- so an unattended run (--config-file) can reach the menu's bare read
with nobody there to answer it. configure_build_environment's own "no LANG= in
locale.conf" branch shows archsetup expects that state to occur. Unattended
therefore takes the menu's default, the same ruling nvidia_preflight and
configure_autologin make for their prompts.

These tests exercise the REAL function body, extracted from the `archsetup`
script at run time (not a copy), against a fixture locale.conf.

Run from repo root:
    python3 -m unittest tests.installer-steps.test_select_locale
"""

import os
import shutil
import subprocess
import tempfile
import unittest


REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
ARCHSETUP = os.path.join(REPO_ROOT, "archsetup")


class SelectLocaleHarness(unittest.TestCase):
    """Source select_locale out of the real archsetup script."""

    def setUp(self):
        self.tmp = tempfile.mkdtemp(prefix="select-locale-test-")
        self.conf = os.path.join(self.tmp, "locale.conf")
        self.wrapper = os.path.join(self.tmp, "run.sh")
        with open(self.wrapper, "w") as f:
            f.write(
                "#!/bin/bash\n"
                'ARCHSETUP="$1"; shift\n'
                "source <(sed -n "
                "'/^select_locale() {/,/^}/p' \"$ARCHSETUP\")\n"
                'select_locale "$LOCALE_CONF"\n'
                'echo "RESOLVED=$locale"\n'
                # Whatever the prompt did not consume is still on stdin.
                'if IFS= read -r leftover; then echo "LEFTOVER:$leftover"; '
                'else echo "LEFTOVER:"; fi\n'
            )
        os.chmod(self.wrapper, 0o755)

    def tearDown(self):
        shutil.rmtree(self.tmp, ignore_errors=True)

    def write_conf(self, body):
        with open(self.conf, "w") as f:
            f.write(body)

    def run_select(self, locale="", config_file="", stdin=""):
        env = dict(os.environ)
        env["locale"] = locale
        env["config_file"] = config_file
        env["LOCALE_CONF"] = self.conf
        return subprocess.run(
            ["bash", self.wrapper, ARCHSETUP],
            input=stdin, capture_output=True, text=True, env=env,
        )

    def resolved(self, r):
        for line in r.stdout.splitlines():
            if line.startswith("RESOLVED="):
                return line.split("=", 1)[1]
        self.fail("no RESOLVED line; stdout=%r stderr=%r" % (r.stdout, r.stderr))

    # ---------------------------------------------------------- normal ----
    def test_existing_locale_conf_wins_and_does_not_prompt(self):
        self.write_conf("LANG=de_DE.UTF-8\n")
        r = self.run_select(stdin="sentinel\n")
        self.assertIn("[OK] Locale: de_DE.UTF-8", r.stdout)
        # locale.conf is authoritative, so the global is deliberately left
        # unset -- configure_build_environment reads LANG out of the file.
        self.assertEqual(self.resolved(r), "")
        self.assertIn("LEFTOVER:sentinel", r.stdout)

    def test_config_file_locale_used_when_conf_has_no_lang(self):
        self.write_conf("")
        r = self.run_select(locale="fr_FR.UTF-8", stdin="sentinel\n")
        self.assertIn("(from config)", r.stdout)
        self.assertEqual(self.resolved(r), "fr_FR.UTF-8")
        self.assertIn("LEFTOVER:sentinel", r.stdout)

    def test_menu_choice_selects_that_locale(self):
        self.write_conf("")
        r = self.run_select(stdin="3\nsentinel\n")
        self.assertEqual(self.resolved(r), "de_DE.UTF-8")
        self.assertIn("LEFTOVER:sentinel", r.stdout)

    # -------------------------------------------------------- boundary ----
    def test_missing_locale_conf_falls_through_to_the_menu(self):
        # No file at all, not just no LANG= line.
        r = self.run_select(stdin="2\nsentinel\n")
        self.assertEqual(self.resolved(r), "en_GB.UTF-8")
        self.assertIn("LEFTOVER:sentinel", r.stdout)

    def test_bare_enter_takes_choice_one(self):
        self.write_conf("")
        r = self.run_select(stdin="\nsentinel\n")
        self.assertEqual(self.resolved(r), "en_US.UTF-8")
        self.assertIn("LEFTOVER:sentinel", r.stdout)

    def test_unattended_config_file_takes_the_default_without_reading(self):
        # THE BUG. --config-file is the documented unattended mode and LOCALE
        # is optional, so this is an ordinary unattended install on a machine
        # whose locale.conf carries no LANG=.
        self.write_conf("")
        r = self.run_select(config_file="/etc/archsetup.conf",
                            stdin="sentinel\n")
        self.assertEqual(self.resolved(r), "en_US.UTF-8")
        self.assertIn("LEFTOVER:sentinel", r.stdout)

    def test_unattended_still_prefers_an_existing_locale_conf(self):
        self.write_conf("LANG=ja_JP.UTF-8\n")
        r = self.run_select(config_file="/etc/archsetup.conf",
                            stdin="sentinel\n")
        self.assertIn("[OK] Locale: ja_JP.UTF-8", r.stdout)
        self.assertIn("LEFTOVER:sentinel", r.stdout)

    def test_unattended_still_prefers_the_configured_locale(self):
        # Unattended does not mean "always en_US" -- LOCALE is an answer, and
        # the menu default must not override it.
        self.write_conf("")
        r = self.run_select(locale="pt_BR.UTF-8",
                            config_file="/etc/archsetup.conf", stdin="sentinel\n")
        self.assertEqual(self.resolved(r), "pt_BR.UTF-8")
        self.assertIn("LEFTOVER:sentinel", r.stdout)

    # ----------------------------------------------------------- error ----
    def test_unrecognised_choice_falls_back_to_en_us(self):
        self.write_conf("")
        r = self.run_select(stdin="42\nsentinel\n")
        self.assertEqual(self.resolved(r), "en_US.UTF-8")
        self.assertIn("LEFTOVER:sentinel", r.stdout)

    def test_manual_entry_reads_a_second_line(self):
        self.write_conf("")
        r = self.run_select(stdin="9\nnl_NL.UTF-8\nsentinel\n")
        self.assertEqual(self.resolved(r), "nl_NL.UTF-8")
        self.assertIn("LEFTOVER:sentinel", r.stdout)

    def test_manual_entry_left_empty_falls_back_to_en_us(self):
        self.write_conf("")
        r = self.run_select(stdin="9\n\nsentinel\n")
        self.assertEqual(self.resolved(r), "en_US.UTF-8")
        self.assertIn("LEFTOVER:sentinel", r.stdout)


if __name__ == "__main__":
    unittest.main()