Compare commits

..

24 Commits

Author SHA1 Message Date
fb5433d473 Bumped version to 1.7.5 2017-10-06 12:39:38 +02:00
a17fe45712 Allow overriding the render output path on a per-scene basis. 2017-10-06 12:39:18 +02:00
1bfba64bdc Formatting 2017-10-06 12:38:17 +02:00
cdb4bf4f4f Renamed 'Job File Path' to 'Job Storage Path' so it's more explicit. 2017-10-06 12:37:44 +02:00
15254b8951 Sorting the project list in user prefs alphabetically 2017-10-06 12:35:25 +02:00
3ed5f2c187 Bumped version to 1.7.4 2017-09-05 11:26:52 +02:00
0be3bf7f49 Fixed unit test, it still mocked sys.platform
We now use platform.system() to detect the platform.
2017-09-05 11:25:25 +02:00
f207e14664 Added link to changelog 2017-09-05 11:16:22 +02:00
9932003400 Fix T48852: screenshot always shows "Communicating with Blender Cloud" 2017-09-05 11:16:17 +02:00
e7035e6f0c Updated changelog 2017-09-05 11:11:31 +02:00
014a36d24e Fix T52621: class name collision upon add-on registration
This is checked since Blender 2.79.
2017-09-05 11:07:33 +02:00
068451a7aa Mark 1.7.3 as released in changelog 2017-09-05 11:06:28 +02:00
56fb1ec3df Bumped version to 1.7.3 2017-08-08 12:46:07 +02:00
e93094cb88 Default to scene frame range when no frame range is given. 2017-07-03 11:09:31 +02:00
33718a1a35 Removed test print statement 2017-07-03 11:09:00 +02:00
db82dbe730 Updated changelog 2017-07-03 09:16:01 +02:00
8d405330ee Better platform detection.
The sys.platform string is 'win32' even on 64-bit Windows. Furthermore,
we expect 'windows', not 'win32'. platform.system().lower() gives us this.
2017-07-03 09:14:27 +02:00
66ddc7b47b Fixed issue running BAM on Windows.
I found this solution in a Django bug report:
 https://code.djangoproject.com/ticket/24160
2017-07-03 09:13:49 +02:00
2fa8cb4054 Refuse to render on Flamenco before blend file is saved at least once.
The file should have a location on the filesystem before BAM can pick it up.
2017-07-03 08:41:26 +02:00
e7b5c75046 Bumped version to 1.7.2 2017-06-22 15:09:39 +02:00
1d93bd9e5e Allow reloading of the Flamenco module with F8 2017-06-22 15:08:30 +02:00
ac2d0c033c Added missing parameter to function call 2017-06-22 15:08:30 +02:00
61fa63eb1d Compatibility fixes for Blender 2.78c
Blender 2.78c is shipped with a version of the io_blend_utils module that
doesn't have a `pythonpath()` function yet, and that's bundled with an
older version of BAM. To work around this, we ship BAM as wheel, and detect
whether this version is needed to run.

As an added bonus, Blender 2.78c can now also use the file exclude filter
for Flamenco. The `bam_supports_exclude_option()` function is thus no
longer necessary.
2017-06-22 15:08:30 +02:00
7022412889 Allow Pillar server URL overriding from environment 2017-06-14 12:56:34 +02:00
12 changed files with 237 additions and 73 deletions

View File

@@ -1,5 +1,31 @@
# Blender Cloud changelog
## Version 1.7.5 (2017-10-06)
- Sorting the project list alphabetically.
- Renamed 'Job File Path' to 'Job Storage Path' so it's more explicit.
- Allow overriding the render output path on a per-scene basis.
## Version 1.7.4 (2017-09-05)
- Fix [T52621](https://developer.blender.org/T52621): Fixed class name collision upon add-on
registration. This is checked since Blender 2.79.
- Fix [T48852](https://developer.blender.org/T48852): Screenshot no longer shows "Communicating with
Blender Cloud".
## Version 1.7.3 (2017-08-08)
- Default to scene frame range when no frame range is given.
- Refuse to render on Flamenco before blend file is saved at least once.
- Fixed some Windows-specific issues.
## Version 1.7.2 (2017-06-22)
- Fixed compatibility with Blender 2.78c.
## Version 1.7.1 (2017-06-13)

View File

@@ -21,7 +21,7 @@
bl_info = {
'name': 'Blender Cloud',
"author": "Sybren A. Stüvel, Francesco Siddi, Inês Almeida, Antony Riakiotakis",
'version': (1, 7, 1),
'version': (1, 7, 5),
'blender': (2, 77, 0),
'location': 'Addon Preferences panel, and Ctrl+Shift+Alt+A anywhere for texture browser',
'description': 'Texture library browser and Blender Sync. Requires the Blender ID addon '

View File

@@ -173,7 +173,7 @@ class AttractPollMixin:
return attract_is_active
class ToolsPanel(AttractPollMixin, Panel):
class AttractToolsPanel(AttractPollMixin, Panel):
bl_label = 'Attract'
bl_space_type = 'SEQUENCE_EDITOR'
bl_region_type = 'UI'
@@ -974,7 +974,7 @@ def register():
bpy.types.SEQUENCER_PT_edit.append(draw_strip_movie_meta)
bpy.utils.register_class(ToolsPanel)
bpy.utils.register_class(AttractToolsPanel)
bpy.utils.register_class(AttractShotRelink)
bpy.utils.register_class(AttractShotDelete)
bpy.utils.register_class(AttractStripUnlink)

View File

@@ -32,8 +32,7 @@ import rna_prop_ui
from . import pillar, async_loop, flamenco
from .utils import pyside_cache, redraw
PILLAR_WEB_SERVER_URL = 'https://cloud.blender.org/'
# PILLAR_WEB_SERVER_URL = 'http://pillar-web:5001/'
PILLAR_WEB_SERVER_URL = os.environ.get('BCLOUD_SERVER', 'https://cloud.blender.org/')
PILLAR_SERVER_URL = '%sapi/' % PILLAR_WEB_SERVER_URL
ADDON_NAME = 'blender_cloud'
@@ -237,7 +236,7 @@ class BlenderCloudPreferences(AddonPreferences):
# NOTE: The assumption is that the workers can also find the files in the same path.
# This assumption is true for the Blender Institute.
flamenco_job_file_path = StringProperty(
name='Job File Path',
name='Job Storage Path',
description='Path where to store job files, should be accesible for Workers too',
subtype='DIR_PATH',
default='/render/_flamenco/storage')
@@ -462,16 +461,7 @@ class BlenderCloudPreferences(AddonPreferences):
path_box.prop(self, 'flamenco_job_output_path', text='')
props = path_box.operator('flamenco.explore_file_path', text='', icon='DISK_DRIVE')
props.path = self.flamenco_job_output_path
show_warning = bool(self.flamenco_exclude_filter and
not bam_interface.bam_supports_exclude_option())
job_output_box.alert = show_warning
job_output_box.prop(self, 'flamenco_exclude_filter',
icon='ERROR' if show_warning else 'NONE')
if show_warning:
job_output_box.label(
text='Warning, the exclusion filter requires a newer version of Blender!')
job_output_box.alert = False
job_output_box.prop(self, 'flamenco_exclude_filter')
prop_split = job_output_box.split(0.32, align=True)
prop_split.label('Strip Components:')
@@ -581,7 +571,7 @@ class PILLAR_OT_projects(async_loop.AsyncModalOperatorMixin,
pillarsdk.Project.all,
{'where': {'user': self.user_id,
'category': {'$ne': 'home'}},
'sort': '-_created',
'sort': '-name',
'projection': {'_id': True,
'name': True,
'extension_props': True},
@@ -591,7 +581,7 @@ class PILLAR_OT_projects(async_loop.AsyncModalOperatorMixin,
pillarsdk.Project.all,
{'where': {'user': {'$ne': self.user_id},
'permissions.groups.group': {'$in': self.db_user.groups}},
'sort': '-_created',
'sort': '-name',
'projection': {'_id': True,
'name': True,
'extension_props': True},
@@ -615,7 +605,10 @@ class PILLAR_OT_projects(async_loop.AsyncModalOperatorMixin,
projects = list(reduce_properties(projects_user['_items'])) + \
list(reduce_properties(projects_shared['_items']))
preferences().project.available_projects = projects
def proj_sort_key(project):
return project.get('name')
preferences().project.available_projects = sorted(projects, key=proj_sort_key)
self.quit()

View File

@@ -20,11 +20,24 @@
The preferences are managed blender.py, the rest of the Flamenco-specific stuff is here.
"""
import functools
import logging
import os
from pathlib import Path, PurePath
import typing
if "bpy" in locals():
import importlib
try:
bam_interface = importlib.reload(bam_interface)
sdk = importlib.reload(sdk)
except NameError:
from . import bam_interface, sdk
else:
from . import bam_interface, sdk
import bpy
from bpy.types import AddonPreferences, Operator, WindowManager, Scene, PropertyGroup
from bpy.props import StringProperty, EnumProperty, PointerProperty, BoolProperty, IntProperty
@@ -136,6 +149,14 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
log = logging.getLogger('%s.FLAMENCO_OT_render' % __name__)
async def async_execute(self, context):
# Refuse to start if the file hasn't been saved. It's okay if
# it's dirty, but we do need a filename and a location.
if not os.path.exists(context.blend_data.filepath):
self.report({'ERROR'}, 'Please save your Blend file before using '
'the Blender Cloud addon.')
self.quit()
return
if not await self.authenticate(context):
return
@@ -178,10 +199,12 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
# Create the job at Flamenco Server.
context.window_manager.flamenco_status = 'COMMUNICATING'
frame_range = scene.flamenco_render_frame_range.strip() or scene_frame_range(context)
settings = {'blender_cmd': '{blender}',
'chunk_size': scene.flamenco_render_fchunk_size,
'filepath': manager.replace_path(outfile),
'frames': scene.flamenco_render_frame_range,
'frames': frame_range,
'render_output': manager.replace_path(render_output),
}
@@ -301,7 +324,6 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
from datetime import datetime
from ..blender import preferences
from . import bam_interface
prefs = preferences()
@@ -335,6 +357,13 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
return outfile, missing_sources
def scene_frame_range(context) -> str:
"""Returns the frame range string for the current scene."""
s = context.scene
return '%i-%i' % (s.frame_start, s.frame_end)
class FLAMENCO_OT_scene_to_frame_range(FlamencoPollMixin, Operator):
"""Sets the scene frame range as the Flamenco render frame range."""
bl_idname = 'flamenco.scene_to_frame_range'
@@ -342,8 +371,7 @@ class FLAMENCO_OT_scene_to_frame_range(FlamencoPollMixin, Operator):
bl_description = __doc__.rstrip('.')
def execute(self, context):
s = context.scene
s.flamenco_render_frame_range = '%i-%i' % (s.frame_start, s.frame_end)
context.scene.flamenco_render_frame_range = scene_frame_range(context)
return {'FINISHED'}
@@ -359,14 +387,15 @@ class FLAMENCO_OT_copy_files(Operator,
async def async_execute(self, context):
from pathlib import Path
from . import bam_interface
from ..blender import preferences
context.window_manager.flamenco_status = 'PACKING'
exclusion_filter = preferences().flamenco_exclude_filter or None
missing_sources = await bam_interface.bam_copy(
Path(context.blend_data.filepath),
Path(preferences().flamenco_job_file_path),
exclusion_filter
)
if missing_sources:
@@ -423,6 +452,30 @@ class FLAMENCO_OT_explore_file_path(FlamencoPollMixin,
return {'FINISHED'}
class FLAMENCO_OT_enable_output_path_override(Operator):
"""Enables the 'override output path' setting."""
bl_idname = 'flamenco.enable_output_path_override'
bl_label = 'Enable Overriding of Output Path'
bl_description = 'Click to specify a non-default Output Path for this particular job'
def execute(self, context):
context.scene.flamenco_do_override_output_path = True
return {'FINISHED'}
class FLAMENCO_OT_disable_output_path_override(Operator):
"""Disables the 'override output path' setting."""
bl_idname = 'flamenco.disable_output_path_override'
bl_label = 'disable Overriding of Output Path'
bl_description = 'Click to use the default Output Path'
def execute(self, context):
context.scene.flamenco_do_override_output_path = False
return {'FINISHED'}
async def create_job(user_id: str,
project_id: str,
manager_id: str,
@@ -477,6 +530,7 @@ def _render_output_path(
flamenco_job_output_path: str,
render_image_format: str,
flamenco_render_frame_range: str,
include_rel_path: bool = True,
) -> typing.Optional[PurePath]:
"""Cached version of render_output_path()
@@ -501,8 +555,7 @@ def _render_output_path(
except ValueError:
return None
rel_parts = proj_rel.parts[flamenco_job_output_strip_components:]
output_top = Path(flamenco_job_output_path)
output_top = PurePath(flamenco_job_output_path)
# Strip off '.flamenco' too; we use 'xxx.flamenco.blend' as job file, but
# don't want to have all the output paths ending in '.flamenco'.
@@ -510,7 +563,11 @@ def _render_output_path(
if stem.endswith('.flamenco'):
stem = stem[:-9]
dir_components = output_top.joinpath(*rel_parts) / stem
if include_rel_path:
rel_parts = proj_rel.parts[flamenco_job_output_strip_components:]
dir_components = output_top.joinpath(*rel_parts) / stem
else:
dir_components = output_top
# Blender will have to append the file extensions by itself.
if is_image_type(render_image_format):
@@ -535,13 +592,19 @@ def render_output_path(context, filepath: Path = None) -> typing.Optional[PurePa
if filepath is None:
filepath = Path(context.blend_data.filepath)
if scene.flamenco_do_override_output_path:
job_output_path = scene.flamenco_override_output_path
else:
job_output_path = prefs.flamenco_job_output_path
return _render_output_path(
prefs.cloud_project_local_path,
filepath,
prefs.flamenco_job_output_strip_components,
prefs.flamenco_job_output_path,
job_output_path,
scene.render.image_settings.file_format,
scene.flamenco_render_frame_range,
include_rel_path=not scene.flamenco_do_override_output_path,
)
@@ -575,8 +638,9 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
if getattr(context.scene, 'flamenco_render_job_type', None) == 'blender-render-progressive':
layout.prop(context.scene, 'flamenco_render_schunk_count')
readonly_stuff = layout.column(align=True)
labeled_row = readonly_stuff.split(0.25, align=True)
paths_layout = layout.column(align=True)
labeled_row = paths_layout.split(0.25, align=True)
labeled_row.label('Storage:')
prop_btn_row = labeled_row.row(align=True)
prop_btn_row.label(prefs.flamenco_job_file_path)
@@ -584,19 +648,33 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
text='', icon='DISK_DRIVE')
props.path = prefs.flamenco_job_file_path
labeled_row = readonly_stuff.split(0.25, align=True)
labeled_row.label('Output:')
prop_btn_row = labeled_row.row(align=True)
render_output = render_output_path(context)
if render_output is None:
prop_btn_row.label('Unable to render with Flamenco, outside of project directory.')
paths_layout.label('Unable to render with Flamenco, outside of project directory.')
else:
prop_btn_row.label(str(render_output))
labeled_row = paths_layout.split(0.25, align=True)
labeled_row.label('Output:')
prop_btn_row = labeled_row.row(align=True)
if context.scene.flamenco_do_override_output_path:
prop_btn_row.prop(context.scene, 'flamenco_override_output_path', text='')
op = FLAMENCO_OT_disable_output_path_override.bl_idname
icon = 'X'
else:
prop_btn_row.label(str(render_output))
op = FLAMENCO_OT_enable_output_path_override.bl_idname
icon = 'GREASEPENCIL'
prop_btn_row.operator(op, icon=icon, text='')
props = prop_btn_row.operator(FLAMENCO_OT_explore_file_path.bl_idname,
text='', icon='DISK_DRIVE')
props.path = str(render_output.parent)
if context.scene.flamenco_do_override_output_path:
labeled_row = paths_layout.split(0.25, align=True)
labeled_row.label('Effective Output Path:')
labeled_row.label(str(render_output))
flamenco_status = context.window_manager.flamenco_status
if flamenco_status == 'IDLE':
layout.operator(FLAMENCO_OT_render.bl_idname,
@@ -628,6 +706,22 @@ def deactivate():
_render_output_path.cache_clear()
def flamenco_do_override_output_path_updated(scene, context):
"""Set the override paths to the default, if not yet set."""
# Only set a default when enabling the override.
if not scene.flamenco_do_override_output_path:
return
# Don't overwrite existing setting.
if scene.flamenco_override_output_path:
return
from ..blender import preferences
scene.flamenco_override_output_path = preferences().flamenco_job_output_path
log.info('Setting Override Output Path to %s', scene.flamenco_override_output_path)
def register():
from ..utils import redraw
@@ -637,6 +731,8 @@ def register():
bpy.utils.register_class(FLAMENCO_OT_scene_to_frame_range)
bpy.utils.register_class(FLAMENCO_OT_copy_files)
bpy.utils.register_class(FLAMENCO_OT_explore_file_path)
bpy.utils.register_class(FLAMENCO_OT_enable_output_path_override)
bpy.utils.register_class(FLAMENCO_OT_disable_output_path_override)
bpy.utils.register_class(FLAMENCO_PT_render)
scene = bpy.types.Scene
@@ -674,6 +770,19 @@ def register():
description='Higher numbers mean higher priority'
)
scene.flamenco_do_override_output_path = BoolProperty(
name='Override Output Path for this Job',
description='When enabled, allows you to specify a non-default Output path '
'for this particular job',
default=False,
update=flamenco_do_override_output_path_updated
)
scene.flamenco_override_output_path = StringProperty(
name='Override Output Path',
description='Path where to store output files, should be accessible for Workers',
subtype='DIR_PATH',
default='')
bpy.types.WindowManager.flamenco_status = EnumProperty(
items=[
('IDLE', 'IDLE', 'Not doing anything.'),
@@ -710,6 +819,14 @@ def unregister():
del bpy.types.Scene.flamenco_render_job_priority
except AttributeError:
pass
try:
del bpy.types.Scene.flamenco_do_override_output_path
except AttributeError:
pass
try:
del bpy.types.Scene.flamenco_override_output_path
except AttributeError:
pass
try:
del bpy.types.WindowManager.flamenco_status
except AttributeError:

View File

@@ -1,6 +1,5 @@
"""BAM packing interface for Flamenco."""
import functools
import logging
from pathlib import Path
import typing
@@ -15,26 +14,26 @@ class CommandExecutionError(Exception):
pass
if 'bam_supports_exclude_option' in locals():
locals()['bam_supports_exclude_option'].cache_clear()
def wheel_pythonpath_278() -> str:
"""Returns the value of a PYTHONPATH environment variable needed to run BAM from its wheel file.
@functools.lru_cache(maxsize=1)
def bam_supports_exclude_option() -> bool:
"""Returns True if the version of BAM bundled with Blender supports --exclude.
This feature was added to BAM 1.1.7, so we can do a simple version check.
Workaround for Blender 2.78c not having io_blend_utils.pythonpath()
"""
try:
import io_blend_utils
except ImportError:
# If this happens, BAM won't work at all. However, this function can be called from
# the GUI; by being a bit careful while importing, we avoid breaking Blender's GUI.
log.exception('Error importing io_blend_utils module.')
return False
import os
from ..wheels import wheel_filename
return io_blend_utils.bl_info['version'] >= (1, 1, 7)
# Find the wheel to run.
wheelpath = wheel_filename('blender_bam')
log.info('Using wheel %s to run BAM-Pack', wheelpath)
# Update the PYTHONPATH to include that wheel.
existing_pypath = os.environ.get('PYTHONPATH', '')
if existing_pypath:
return os.pathsep.join((existing_pypath, wheelpath))
return wheelpath
async def bam_copy(base_blendfile: Path, target_blendfile: Path,
@@ -51,6 +50,7 @@ async def bam_copy(base_blendfile: Path, target_blendfile: Path,
"""
import asyncio
import os
import shlex
import subprocess
@@ -66,18 +66,28 @@ async def bam_copy(base_blendfile: Path, target_blendfile: Path,
]
if exclusion_filter:
if bam_supports_exclude_option():
args.extend(['--exclude', exclusion_filter])
else:
log.warning('Your version of Blender does not support the exclusion filter, '
'copying all files.')
args.extend(['--exclude', exclusion_filter])
cmd_to_log = ' '.join(shlex.quote(s) for s in args)
log.info('Executing %s', cmd_to_log)
# Workaround for Blender 2.78c not having io_blend_utils.pythonpath()
if hasattr(io_blend_utils, 'pythonpath'):
pythonpath = io_blend_utils.pythonpath()
else:
pythonpath = wheel_pythonpath_278()
env = {
'PYTHONPATH': pythonpath,
# Needed on Windows because http://bugs.python.org/issue8557
'PATH': os.environ['PATH'],
}
if 'SYSTEMROOT' in os.environ: # Windows http://bugs.python.org/issue20614
env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
proc = await asyncio.create_subprocess_exec(
*args,
env={'PYTHONPATH': io_blend_utils.pythonpath()},
env=env,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,

View File

@@ -11,21 +11,20 @@ class Manager(List, Find):
@functools.lru_cache()
def _sorted_path_replacements(self) -> list:
import sys
import platform
if self.path_replacement is None:
return []
print('SORTING PATH REPLACEMENTS')
items = self.path_replacement.to_dict().items()
def by_length(item):
return -len(item[0]), item[0]
platform = sys.platform
return [(varname, platform_replacements[platform])
for varname, platform_replacements in sorted(items, key=by_length)]
this_platform = platform.system().lower()
return [(varname, platform_replacements[this_platform])
for varname, platform_replacements in sorted(items, key=by_length)
if this_platform in platform_replacements]
def replace_path(self, some_path: pathlib.PurePath) -> str:
"""Performs path variable replacement.

View File

@@ -112,7 +112,11 @@ class PILLAR_OT_image_share(pillar.PillarOperatorMixin,
async def async_execute(self, context):
"""Entry point of the asynchronous operator."""
self.report({'INFO'}, 'Communicating with Blender Cloud')
# We don't want to influence what is included in the screen shot.
if self.target == 'SCREENSHOT':
print('Blender Cloud add-on is communicating with Blender Cloud')
else:
self.report({'INFO'}, 'Communicating with Blender Cloud')
try:
# Refresh credentials

View File

@@ -44,6 +44,12 @@ def load_wheel(module_name, fname_prefix):
module_name, module.__file__, fname_prefix)
return
sys.path.append(wheel_filename(fname_prefix))
module = __import__(module_name)
log.debug('Loaded %s from %s', module_name, module.__file__)
def wheel_filename(fname_prefix: str) -> str:
path_pattern = os.path.join(my_dir, '%s*.whl' % fname_prefix)
wheels = glob.glob(path_pattern)
if not wheels:
@@ -51,9 +57,7 @@ def load_wheel(module_name, fname_prefix):
# If there are multiple wheels that match, load the latest one.
wheels.sort()
sys.path.append(wheels[-1])
module = __import__(module_name)
log.debug('Loaded %s from %s', module_name, module.__file__)
return wheels[-1]
def load_wheels():

View File

@@ -3,6 +3,7 @@
lockfile==0.12.2
pillarsdk==1.6.1
wheel==0.29.0
blender-bam==1.1.7
# Secondary requirements:
cffi==1.6.0

View File

@@ -101,6 +101,11 @@ class BuildWheels(Command):
log.info('Downloading Pillar Python SDK wheel')
self.download_wheel(requirements['pillarsdk'])
# Download BAM from pypi. This is required for compatibility with Blender 2.78.
if not list(self.wheels_path.glob('blender_bam*.whl')):
log.info('Downloading BAM wheel')
self.download_wheel(requirements['blender-bam'])
# Build CacheControl.
if not list(self.wheels_path.glob('CacheControl*.whl')):
log.info('Building CacheControl in %s', self.cachecontrol_path)
@@ -227,7 +232,7 @@ setup(
'wheels': BuildWheels},
name='blender_cloud',
description='The Blender Cloud addon allows browsing the Blender Cloud from Blender.',
version='1.7.1',
version='1.7.5',
author='Sybren A. Stüvel',
author_email='sybren@stuvel.eu',
packages=find_packages('.'),

View File

@@ -83,8 +83,13 @@ class PathReplacementTest(unittest.TestCase):
def _do_test(self, test_paths, platform, pathclass):
self.test_manager.PurePlatformPath = pathclass
with unittest.mock.patch('sys.platform', platform):
def mocked_system():
return platform
with unittest.mock.patch('platform.system', mocked_system):
for expected_result, input_path in test_paths:
as_path_instance = pathclass(input_path)
self.assertEqual(expected_result,
self.test_manager.replace_path(pathclass(input_path)),
'for input %s on platform %s' % (input_path, platform))
self.test_manager.replace_path(as_path_instance),
'for input %r on platform %s' % (as_path_instance, platform))