Merge ~chad.smith/cloud-init:bug/1840080-ubuntu-drivers-emit-latelink-v2 into cloud-init:master

Proposed by Chad Smith on 2019-08-20
Status: Merged
Approved by: Dan Watkins on 2019-08-22
Approved revision: 2eb8eee7281a84a832f865a7ddd0edabe534e987
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~chad.smith/cloud-init:bug/1840080-ubuntu-drivers-emit-latelink-v2
Merge into: cloud-init:master
Diff against target: 310 lines (+130/-36)
3 files modified
cloudinit/config/cc_ubuntu_drivers.py (+48/-10)
cloudinit/config/tests/test_ubuntu_drivers.py (+80/-25)
cloudinit/tests/helpers.py (+2/-1)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve on 2019-08-22
cloud-init Commiters 2019-08-20 Pending
Review via email: mp+371546@code.launchpad.net

Commit message

ubuntu-drivers: call db_x_loadtemplatefile to accept NVIDIA EULA

Emit a script allowing cloud-init to set linux/nvidia/latelink
debconf selection to true. This avoids having to call
debconf-set-selections and allows cloud-init to pre-confgure
linux-restricted-modules to link NVIDIA drivers to the running kernel.

Cloud-init loads this debconf template and sets the value to true in the
debconf database by sourcing debconf's /usr/share/debconf/confmodule and
uses db_x_loadtemplatefile to register cloud-init's setting for
linux/nvidia/latelink.

LP: #1840080

To post a comment you must log in.

FAILED: Continuous integration, rev:044f9d692ebca82186b9159a69e75816ad99f998
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1060/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1060//rebuild

review: Needs Fixing (continuous-integration)
Ryan Harper (raharper) wrote :

some inline comments/suggestions.

Chad Smith (chad.smith) :

FAILED: Continuous integration, rev:71fcd42be6330033bcb731080053a885786f2130
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1061/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1061//rebuild

review: Needs Fixing (continuous-integration)

PASSED: Continuous integration, rev:48733aa42ac30013db4ce3daa7643ee941b51b63
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1062/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1062//rebuild

review: Approve (continuous-integration)

FAILED: Continuous integration, rev:cdd846983dc8d3dca53d3c37d69c0c967787a7ae
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1063/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    FAILED: Ubuntu LTS: Integration

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1063//rebuild

review: Needs Fixing (continuous-integration)
Chad Smith (chad.smith) wrote :

Just validated on Eoan that this package properly sets up linux/nvidia/latelink true via debconf-get-selections.

The ubuntu-drivers-common package fixes has not yet published to Eoan. So, end-to-end validation will not be possible until an updated ubuntu-drivers-common and linux-modules-nvidia* is published.

As it is, this behavior paves the way for the ubuntu-drivers-common/linux-modules-nvidia package features.

# cloud-init properly registers and sets linux/nvidia/latelink : true
ubuntu@ip-172-31-30-68:~$ sudo debconf-get-selections | egrep 'cloud-init|late'
cloud-init linux/nvidia/latelink boolean true
cloud-init cloud-init/datasources multiselect NoCloud, ConfigDrive, OpenNebula, DigitalOcean, Azure, AltCloud, OVF, MAAS, GCE, OpenStack, CloudSigma, SmartOS, Bigstep, Scaleway, AliYun, Ec2, CloudStack, Hetzner, IBMCloud, None

# ubuntu-drivers-common was installed, but no linux-modules-nvidia-* yet
dpkg -l | egrep 'cloud-init|nvidia|ubuntu-drivers'
ii cloud-init 19.2-21-gcdd84698-1~bddeb all Init scripts for cloud instances
ii cloud-initramfs-copymods 0.44ubuntu1 all copy initramfs modules into root filesystem for later use
ii cloud-initramfs-dyn-netconf 0.44ubuntu1 all write a network interface file in /run for BOOTIF
ii libnvidia-cfg1-430:amd64 430.40-0ubuntu2 amd64 NVIDIA binary OpenGL/GLX configuration library
ii libnvidia-compute-430:amd64 430.40-0ubuntu2 amd64 NVIDIA libcompute package
ii nvidia-compute-utils-430 430.40-0ubuntu2 amd64 NVIDIA compute utilities
ii nvidia-headless-no-dkms-430 430.40-0ubuntu2 amd64 NVIDIA headless metapackage - no DKMS
ii nvidia-kernel-source-430 430.40-0ubuntu2 amd64 NVIDIA kernel source package
ii ubuntu-drivers-common 1:0.7.5 amd64 Detect and install additional Ubuntu driver packages

### If we manually install linux-modules-nvidia-430-generic, latelink appears set true
ubuntu@ip-172-31-16-66:~$ sudo debconf-get-selections | grep late
cloud-init linux/nvidia/latelink boolean true
linux-modules-nvidia-430-5.2.0-13-generic linux/nvidia/latelink booleantrue

# from dpkg -l on eoan-proposed
ii linux-modules-nvidia-430-5.2.0-13-generic 5.2.0-13.14 amd64 Linux kernel nvidia modules for version 5.2.0-13

Ryan Harper (raharper) wrote :

Implementation looks great. One suggestion on the template description.

PASSED: Continuous integration, rev:cdd846983dc8d3dca53d3c37d69c0c967787a7ae
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1064/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1064//rebuild

review: Approve (continuous-integration)

FAILED: Continuous integration, rev:8da37261c56c583b1631cbae9910a100c7894c6e
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1065/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1065//rebuild

review: Needs Fixing (continuous-integration)

PASSED: Continuous integration, rev:19dbf5e1449cfa59b9f8fbfb056d182516543eb1
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1066/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1066//rebuild

review: Approve (continuous-integration)

Autolanding: FAILED
More details in the following jenkins job:
https://jenkins.ubuntu.com/server/job/cloud-init-autoland-test/294/
Executed test runs:
    FAILED: Checkout

review: Needs Fixing (continuous-integration)

Autolanding: FAILED
More details in the following jenkins job:
https://jenkins.ubuntu.com/server/job/cloud-init-autoland-test/295/
Executed test runs:
    FAILED: Checkout

review: Needs Fixing (continuous-integration)

Autolanding: FAILED
More details in the following jenkins job:
https://jenkins.ubuntu.com/server/job/cloud-init-autoland-test/296/
Executed test runs:
    FAILED: Checkout

review: Needs Fixing (continuous-integration)
review: Approve (continuous-integration)

Autolanding: FAILED
Unapproved changes made after approval.
https://jenkins.ubuntu.com/server/job/cloud-init-autoland-test/297/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

review: Needs Fixing (continuous-integration)
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/config/cc_ubuntu_drivers.py b/cloudinit/config/cc_ubuntu_drivers.py
2index 4da34ee..297451d 100644
3--- a/cloudinit/config/cc_ubuntu_drivers.py
4+++ b/cloudinit/config/cc_ubuntu_drivers.py
5@@ -2,13 +2,14 @@
6
7 """Ubuntu Drivers: Interact with third party drivers in Ubuntu."""
8
9+import os
10 from textwrap import dedent
11
12-from cloudinit.config import cc_apt_configure
13 from cloudinit.config.schema import (
14 get_schema_doc, validate_cloudconfig_schema)
15 from cloudinit import log as logging
16 from cloudinit.settings import PER_INSTANCE
17+from cloudinit import temp_utils
18 from cloudinit import type_utils
19 from cloudinit import util
20
21@@ -65,6 +66,33 @@ OLD_UBUNTU_DRIVERS_STDERR_NEEDLE = (
22 __doc__ = get_schema_doc(schema) # Supplement python help()
23
24
25+# Use a debconf template to configure a global debconf variable
26+# (linux/nvidia/latelink) setting this to "true" allows the
27+# 'linux-restricted-modules' deb to accept the NVIDIA EULA and the package
28+# will automatically link the drivers to the running kernel.
29+
30+# EOL_XENIAL: can then drop this script and use python3-debconf which is only
31+# available in Bionic and later. Can't use python3-debconf currently as it
32+# isn't in Xenial and doesn't yet support X_LOADTEMPLATEFILE debconf command.
33+
34+NVIDIA_DEBCONF_CONTENT = """\
35+Template: linux/nvidia/latelink
36+Type: boolean
37+Default: true
38+Description: Late-link NVIDIA kernel modules?
39+ Enable this to link the NVIDIA kernel modules in cloud-init and
40+ make them available for use.
41+"""
42+
43+NVIDIA_DRIVER_LATELINK_DEBCONF_SCRIPT = """\
44+#!/bin/sh
45+# Allow cloud-init to trigger EULA acceptance via registering a debconf
46+# template to set linux/nvidia/latelink true
47+. /usr/share/debconf/confmodule
48+db_x_loadtemplatefile "$1" cloud-init
49+"""
50+
51+
52 def install_drivers(cfg, pkg_install_func):
53 if not isinstance(cfg, dict):
54 raise TypeError(
55@@ -90,17 +118,27 @@ def install_drivers(cfg, pkg_install_func):
56 if version_cfg:
57 driver_arg += ':{}'.format(version_cfg)
58
59- LOG.debug("Installing NVIDIA drivers (%s=%s, version=%s)",
60+ LOG.debug("Installing and activating NVIDIA drivers (%s=%s, version=%s)",
61 cfgpath, nv_acc, version_cfg if version_cfg else 'latest')
62
63- # Setting NVIDIA latelink confirms acceptance of EULA for the package
64- # linux-restricted-modules
65- # Reference code defining debconf variable is here
66- # https://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/
67- # linux-restricted-modules/+git/eoan/tree/debian/templates/
68- # nvidia.templates.in
69- selections = b'linux-restricted-modules linux/nvidia/latelink boolean true'
70- cc_apt_configure.debconf_set_selections(selections)
71+ # Register and set debconf selection linux/nvidia/latelink = true
72+ tdir = temp_utils.mkdtemp(needs_exe=True)
73+ debconf_file = os.path.join(tdir, 'nvidia.template')
74+ debconf_script = os.path.join(tdir, 'nvidia-debconf.sh')
75+ try:
76+ util.write_file(debconf_file, NVIDIA_DEBCONF_CONTENT)
77+ util.write_file(
78+ debconf_script,
79+ util.encode_text(NVIDIA_DRIVER_LATELINK_DEBCONF_SCRIPT),
80+ mode=0o755)
81+ util.subp([debconf_script, debconf_file])
82+ except Exception as e:
83+ util.logexc(
84+ LOG, "Failed to register NVIDIA debconf template: %s", str(e))
85+ raise
86+ finally:
87+ if os.path.isdir(tdir):
88+ util.del_dir(tdir)
89
90 try:
91 util.subp(['ubuntu-drivers', 'install', '--gpgpu', driver_arg])
92diff --git a/cloudinit/config/tests/test_ubuntu_drivers.py b/cloudinit/config/tests/test_ubuntu_drivers.py
93index 6a763bd..4695269 100644
94--- a/cloudinit/config/tests/test_ubuntu_drivers.py
95+++ b/cloudinit/config/tests/test_ubuntu_drivers.py
96@@ -1,6 +1,7 @@
97 # This file is part of cloud-init. See LICENSE file for license information.
98
99 import copy
100+import os
101
102 from cloudinit.tests.helpers import CiTestCase, skipUnlessJsonSchema, mock
103 from cloudinit.config.schema import (
104@@ -9,11 +10,27 @@ from cloudinit.config import cc_ubuntu_drivers as drivers
105 from cloudinit.util import ProcessExecutionError
106
107 MPATH = "cloudinit.config.cc_ubuntu_drivers."
108+M_TMP_PATH = MPATH + "temp_utils.mkdtemp"
109 OLD_UBUNTU_DRIVERS_ERROR_STDERR = (
110 "ubuntu-drivers: error: argument <command>: invalid choice: 'install' "
111 "(choose from 'list', 'autoinstall', 'devices', 'debug')\n")
112
113
114+class AnyTempScriptAndDebconfFile(object):
115+
116+ def __init__(self, tmp_dir, debconf_file):
117+ self.tmp_dir = tmp_dir
118+ self.debconf_file = debconf_file
119+
120+ def __eq__(self, cmd):
121+ if not len(cmd) == 2:
122+ return False
123+ script, debconf_file = cmd
124+ if bool(script.startswith(self.tmp_dir) and script.endswith('.sh')):
125+ return debconf_file == self.debconf_file
126+ return False
127+
128+
129 class TestUbuntuDrivers(CiTestCase):
130 cfg_accepted = {'drivers': {'nvidia': {'license-accepted': True}}}
131 install_gpgpu = ['ubuntu-drivers', 'install', '--gpgpu', 'nvidia']
132@@ -28,20 +45,23 @@ class TestUbuntuDrivers(CiTestCase):
133 {'drivers': {'nvidia': {'license-accepted': "TRUE"}}},
134 schema=drivers.schema, strict=True)
135
136- @mock.patch(MPATH + "cc_apt_configure.debconf_set_selections")
137+ @mock.patch(M_TMP_PATH)
138 @mock.patch(MPATH + "util.subp", return_value=('', ''))
139 @mock.patch(MPATH + "util.which", return_value=False)
140 def _assert_happy_path_taken(
141- self, config, m_which, m_subp, m_debconf_set_selections):
142+ self, config, m_which, m_subp, m_tmp):
143 """Positive path test through handle. Package should be installed."""
144+ tdir = self.tmp_dir()
145+ debconf_file = os.path.join(tdir, 'nvidia.template')
146+ m_tmp.return_value = tdir
147 myCloud = mock.MagicMock()
148 drivers.handle('ubuntu_drivers', config, myCloud, None, None)
149 self.assertEqual([mock.call(['ubuntu-drivers-common'])],
150 myCloud.distro.install_packages.call_args_list)
151- self.assertEqual([mock.call(self.install_gpgpu)],
152- m_subp.call_args_list)
153- m_debconf_set_selections.assert_called_with(
154- b'linux-restricted-modules linux/nvidia/latelink boolean true')
155+ self.assertEqual(
156+ [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)),
157+ mock.call(self.install_gpgpu)],
158+ m_subp.call_args_list)
159
160 def test_handle_does_package_install(self):
161 self._assert_happy_path_taken(self.cfg_accepted)
162@@ -52,20 +72,33 @@ class TestUbuntuDrivers(CiTestCase):
163 new_config['drivers']['nvidia']['license-accepted'] = true_value
164 self._assert_happy_path_taken(new_config)
165
166- @mock.patch(MPATH + "cc_apt_configure.debconf_set_selections")
167- @mock.patch(MPATH + "util.subp", side_effect=ProcessExecutionError(
168- stdout='No drivers found for installation.\n', exit_code=1))
169+ @mock.patch(M_TMP_PATH)
170+ @mock.patch(MPATH + "util.subp")
171 @mock.patch(MPATH + "util.which", return_value=False)
172- def test_handle_raises_error_if_no_drivers_found(self, m_which, m_subp, _):
173+ def test_handle_raises_error_if_no_drivers_found(
174+ self, m_which, m_subp, m_tmp):
175 """If ubuntu-drivers doesn't install any drivers, raise an error."""
176+ tdir = self.tmp_dir()
177+ debconf_file = os.path.join(tdir, 'nvidia.template')
178+ m_tmp.return_value = tdir
179 myCloud = mock.MagicMock()
180+
181+ def fake_subp(cmd):
182+ if cmd[0].startswith(tdir):
183+ return
184+ raise ProcessExecutionError(
185+ stdout='No drivers found for installation.\n', exit_code=1)
186+ m_subp.side_effect = fake_subp
187+
188 with self.assertRaises(Exception):
189 drivers.handle(
190 'ubuntu_drivers', self.cfg_accepted, myCloud, None, None)
191 self.assertEqual([mock.call(['ubuntu-drivers-common'])],
192 myCloud.distro.install_packages.call_args_list)
193- self.assertEqual([mock.call(self.install_gpgpu)],
194- m_subp.call_args_list)
195+ self.assertEqual(
196+ [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)),
197+ mock.call(self.install_gpgpu)],
198+ m_subp.call_args_list)
199 self.assertIn('ubuntu-drivers found no drivers for installation',
200 self.logs.getvalue())
201
202@@ -113,19 +146,25 @@ class TestUbuntuDrivers(CiTestCase):
203 myLog.debug.call_args_list[0][0][0])
204 self.assertEqual(0, m_install_drivers.call_count)
205
206- @mock.patch(MPATH + "cc_apt_configure.debconf_set_selections")
207+ @mock.patch(M_TMP_PATH)
208 @mock.patch(MPATH + "util.subp", return_value=('', ''))
209 @mock.patch(MPATH + "util.which", return_value=True)
210- def test_install_drivers_no_install_if_present(self, m_which, m_subp, _):
211+ def test_install_drivers_no_install_if_present(
212+ self, m_which, m_subp, m_tmp):
213 """If 'ubuntu-drivers' is present, no package install should occur."""
214+ tdir = self.tmp_dir()
215+ debconf_file = os.path.join(tdir, 'nvidia.template')
216+ m_tmp.return_value = tdir
217 pkg_install = mock.MagicMock()
218 drivers.install_drivers(self.cfg_accepted['drivers'],
219 pkg_install_func=pkg_install)
220 self.assertEqual(0, pkg_install.call_count)
221 self.assertEqual([mock.call('ubuntu-drivers')],
222 m_which.call_args_list)
223- self.assertEqual([mock.call(self.install_gpgpu)],
224- m_subp.call_args_list)
225+ self.assertEqual(
226+ [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)),
227+ mock.call(self.install_gpgpu)],
228+ m_subp.call_args_list)
229
230 def test_install_drivers_rejects_invalid_config(self):
231 """install_drivers should raise TypeError if not given a config dict"""
232@@ -134,21 +173,33 @@ class TestUbuntuDrivers(CiTestCase):
233 drivers.install_drivers("mystring", pkg_install_func=pkg_install)
234 self.assertEqual(0, pkg_install.call_count)
235
236- @mock.patch(MPATH + "cc_apt_configure.debconf_set_selections")
237- @mock.patch(MPATH + "util.subp", side_effect=ProcessExecutionError(
238- stderr=OLD_UBUNTU_DRIVERS_ERROR_STDERR, exit_code=2))
239+ @mock.patch(M_TMP_PATH)
240+ @mock.patch(MPATH + "util.subp")
241 @mock.patch(MPATH + "util.which", return_value=False)
242 def test_install_drivers_handles_old_ubuntu_drivers_gracefully(
243- self, m_which, m_subp, _):
244+ self, m_which, m_subp, m_tmp):
245 """Older ubuntu-drivers versions should emit message and raise error"""
246+ tdir = self.tmp_dir()
247+ debconf_file = os.path.join(tdir, 'nvidia.template')
248+ m_tmp.return_value = tdir
249 myCloud = mock.MagicMock()
250+
251+ def fake_subp(cmd):
252+ if cmd[0].startswith(tdir):
253+ return
254+ raise ProcessExecutionError(
255+ stderr=OLD_UBUNTU_DRIVERS_ERROR_STDERR, exit_code=2)
256+ m_subp.side_effect = fake_subp
257+
258 with self.assertRaises(Exception):
259 drivers.handle(
260 'ubuntu_drivers', self.cfg_accepted, myCloud, None, None)
261 self.assertEqual([mock.call(['ubuntu-drivers-common'])],
262 myCloud.distro.install_packages.call_args_list)
263- self.assertEqual([mock.call(self.install_gpgpu)],
264- m_subp.call_args_list)
265+ self.assertEqual(
266+ [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)),
267+ mock.call(self.install_gpgpu)],
268+ m_subp.call_args_list)
269 self.assertIn('WARNING: the available version of ubuntu-drivers is'
270 ' too old to perform requested driver installation',
271 self.logs.getvalue())
272@@ -160,17 +211,21 @@ class TestUbuntuDriversWithVersion(TestUbuntuDrivers):
273 'drivers': {'nvidia': {'license-accepted': True, 'version': '123'}}}
274 install_gpgpu = ['ubuntu-drivers', 'install', '--gpgpu', 'nvidia:123']
275
276- @mock.patch(MPATH + "cc_apt_configure.debconf_set_selections")
277+ @mock.patch(M_TMP_PATH)
278 @mock.patch(MPATH + "util.subp", return_value=('', ''))
279 @mock.patch(MPATH + "util.which", return_value=False)
280- def test_version_none_uses_latest(self, m_which, m_subp, _):
281+ def test_version_none_uses_latest(self, m_which, m_subp, m_tmp):
282+ tdir = self.tmp_dir()
283+ debconf_file = os.path.join(tdir, 'nvidia.template')
284+ m_tmp.return_value = tdir
285 myCloud = mock.MagicMock()
286 version_none_cfg = {
287 'drivers': {'nvidia': {'license-accepted': True, 'version': None}}}
288 drivers.handle(
289 'ubuntu_drivers', version_none_cfg, myCloud, None, None)
290 self.assertEqual(
291- [mock.call(['ubuntu-drivers', 'install', '--gpgpu', 'nvidia'])],
292+ [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)),
293+ mock.call(['ubuntu-drivers', 'install', '--gpgpu', 'nvidia'])],
294 m_subp.call_args_list)
295
296 def test_specifying_a_version_doesnt_override_license_acceptance(self):
297diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py
298index f41180f..23fddd0 100644
299--- a/cloudinit/tests/helpers.py
300+++ b/cloudinit/tests/helpers.py
301@@ -198,7 +198,8 @@ class CiTestCase(TestCase):
302 prefix="ci-%s." % self.__class__.__name__)
303 else:
304 tmpd = tempfile.mkdtemp(dir=dir)
305- self.addCleanup(functools.partial(shutil.rmtree, tmpd))
306+ self.addCleanup(
307+ functools.partial(shutil.rmtree, tmpd, ignore_errors=True))
308 return tmpd
309
310 def tmp_path(self, path, dir=None):

Subscribers

People subscribed via source and target branches