generated from stilobique/BlenderTemplate
Initial commit
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"blender": {
|
||||
"moderlab_type": ["moderlab_type.zip", "Moderlab-Production/BlenderObjectType", "prerelease"],
|
||||
"moderlab_pie": ["moderlab_plugin.zip", "Moderlab-Production/BlenderPieMenu"],
|
||||
"moderlab_plugin": ["moderlab_plugin.zip", "Moderlab-Production/BlenderPlugin"]
|
||||
},
|
||||
"unreal": {
|
||||
"unreal-pipeline": ["unreal-moderlab-pipeline.zip", "Moderlab-Production/UnrealPipeline"],
|
||||
"unreal-type": ["Moderlab-Unreal-Type.zip", "Moderlab-Production/UnrealType"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
/opt/blender/blender --background --python-exit-code 1 --python "$FOLDER_TEST/tests/utils/blender_addon.py" > /dev/null 2>&1 || exit 1
|
||||
/opt/blender/blender --background -noaudio --disable-autoexec --python-exit-code 1 --python "$1" -- --verbose || exit 1
|
||||
@@ -0,0 +1,54 @@
|
||||
import sys
|
||||
import os
|
||||
import enum
|
||||
|
||||
from pathlib import Path
|
||||
from utils.blender import get_b3d_addon_dependency
|
||||
from utils.container import VirtualMachine
|
||||
from utils.misc import ordering_test_file, generate_archive
|
||||
from utils.properties import ContainerObject
|
||||
|
||||
|
||||
class Container(enum.Enum):
|
||||
"""Enumerate about the Geometry node"""
|
||||
BLENDER = ContainerObject(name='Blender', image='stilobique/blender', tag='3.1.2')
|
||||
|
||||
|
||||
def launch_unit_test(test: [str] = None):
|
||||
"""Start all Unit Test, Blender and Unreal if needed"""
|
||||
# Ordering Unit test Blender/Unreal
|
||||
if test is None:
|
||||
test = ordering_test_file()
|
||||
b3d = test['blender']
|
||||
else:
|
||||
b3d = test
|
||||
|
||||
vm_b3d = VirtualMachine(Container.BLENDER.value)
|
||||
vm_b3d.launch_unit_test(tests=b3d)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Initialize Variable and module request
|
||||
archives = []
|
||||
|
||||
# Prepare Blender and Unreal dependency
|
||||
get_b3d_addon_dependency(archives)
|
||||
generate_archive(archives, 'blender_addon_folder')
|
||||
|
||||
# Generate Unit Test, a specific call or execute all Unit Test
|
||||
test_list = None
|
||||
if sys.argv:
|
||||
for arg in sys.argv:
|
||||
if '--test=' in arg:
|
||||
test_list = []
|
||||
items = arg.replace('--test=', '').split(',')
|
||||
for item in items:
|
||||
test_list.append(item)
|
||||
|
||||
# Launch Unit Test
|
||||
launch_unit_test(test=test_list)
|
||||
|
||||
# Clear archive file and container
|
||||
for archive in archives:
|
||||
if Path(Path(os.getcwd(), archive)).exists():
|
||||
os.remove(Path(os.getcwd(), archive))
|
||||
@@ -0,0 +1,2 @@
|
||||
docker
|
||||
PyGithub
|
||||
@@ -0,0 +1,25 @@
|
||||
import unittest
|
||||
import bpy
|
||||
import glob
|
||||
import os
|
||||
|
||||
|
||||
class ActivateAddon(unittest.TestCase):
|
||||
"""Activate the Blender addon"""
|
||||
|
||||
@staticmethod
|
||||
def get_folder_name():
|
||||
"""Return the folder name to get the addon name we want activated"""
|
||||
addon = glob.glob("/addon_moderlab/*/__init__.py", recursive=True)
|
||||
return os.path.basename(os.path.dirname(addon[0]))
|
||||
|
||||
def test_activate_addon(self):
|
||||
"""Activate the blender addon 'moderlab_plugin'"""
|
||||
addon = bpy.ops.preferences.addon_enable(module=self.get_folder_name())
|
||||
self.assertEqual({'FINISHED'}, addon)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
|
||||
unittest.main()
|
||||
@@ -0,0 +1,21 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from pathlib import Path
|
||||
from .forge import get_release_file
|
||||
|
||||
|
||||
def get_b3d_addon_dependency(archive: list):
|
||||
"""From json resources, get all blender dependency and download-it"""
|
||||
dependency = Path(os.getcwd(), "tests", "dependency.json")
|
||||
with open(dependency) as f:
|
||||
data = json.load(f)
|
||||
|
||||
b3d_dependency = data['blender']
|
||||
for key, value in b3d_dependency.items():
|
||||
if 'prerelease' in value:
|
||||
prerelease = True
|
||||
else:
|
||||
prerelease = False
|
||||
get_release_file(value[0], value[1], prerelease=prerelease)
|
||||
archive.append(value[0])
|
||||
@@ -0,0 +1,58 @@
|
||||
import os
|
||||
import bpy
|
||||
import pathlib
|
||||
import json
|
||||
import shutil
|
||||
|
||||
|
||||
def b3d_install_addon():
|
||||
"""Function to install addon with the blender locally set"""
|
||||
env_name = 'FOLDER_TEST'
|
||||
dependency = pathlib.Path(os.environ[env_name], "tests", "dependency.json")
|
||||
with open(dependency) as f:
|
||||
data = json.load(f)
|
||||
|
||||
b3d_dependency = data['blender']
|
||||
|
||||
try:
|
||||
if not os.environ.get(env_name):
|
||||
raise KeyError
|
||||
env_path = pathlib.Path(os.environ[env_name])
|
||||
if not env_path.exists():
|
||||
raise FileNotFoundError
|
||||
|
||||
for key, value in b3d_dependency.items():
|
||||
bpy.ops.preferences.addon_install(filepath=f'{env_path}/{value[0]}')
|
||||
bpy.ops.preferences.addon_enable(module=key)
|
||||
bpy.ops.wm.save_userpref()
|
||||
|
||||
except KeyError:
|
||||
print(f'Env. "{env_name}" doesn\'t exist')
|
||||
exit(1)
|
||||
except FileNotFoundError:
|
||||
print('Wrong path to execute this Unit Test')
|
||||
exit(1)
|
||||
|
||||
|
||||
def b3d_install_preset():
|
||||
"""If the folder preset exist, add all preset inside the moderlab dedicated folder"""
|
||||
env_name = 'FOLDER_TEST'
|
||||
|
||||
try:
|
||||
if not os.environ.get(env_name):
|
||||
raise KeyError
|
||||
|
||||
path_preset = pathlib.Path(bpy.utils.preset_paths("")[0])
|
||||
path_locally = pathlib.Path(os.environ.get(env_name), "presets")
|
||||
|
||||
if path_locally.exists():
|
||||
shutil.copytree(path_locally, path_preset.joinpath("moderlab", "props"))
|
||||
|
||||
except KeyError:
|
||||
print(f'Env. "{env_name}" doesn\'t exist')
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
b3d_install_addon()
|
||||
b3d_install_preset()
|
||||
@@ -0,0 +1,75 @@
|
||||
import docker
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pathlib import PurePosixPath
|
||||
from docker.errors import DockerException
|
||||
from .issue import ContainerErrorTest
|
||||
from .properties import ContainerObject
|
||||
|
||||
|
||||
class VirtualMachine:
|
||||
def __init__(self, container: ContainerObject):
|
||||
self.client = self.start_docker()
|
||||
|
||||
self.container = container
|
||||
self.base_command = self.container.commands
|
||||
self.clear_containers()
|
||||
|
||||
@staticmethod
|
||||
def start_docker():
|
||||
"""Generate docker client information"""
|
||||
try:
|
||||
client = docker.from_env()
|
||||
return client
|
||||
|
||||
except DockerException:
|
||||
print('Docker isn\'t start or installed')
|
||||
exit(1)
|
||||
|
||||
def clear_containers(self):
|
||||
"""Look all docker containers, and remove-it if the task are used with the unit test."""
|
||||
containers = self.client.containers.list(all=True)
|
||||
|
||||
for container in containers:
|
||||
image_tag = container.image.tags[0]
|
||||
status = container.status
|
||||
if image_tag in self.container.ref and status in 'exited':
|
||||
container.remove()
|
||||
|
||||
@staticmethod
|
||||
def workspace():
|
||||
"""Define the workspace folder"""
|
||||
if os.environ.get('GITHUB_WORKSPACE'):
|
||||
return os.environ['GITHUB_WORKSPACE']
|
||||
else:
|
||||
return os.getcwd()
|
||||
|
||||
def launch_unit_test(self, tests: list[str]):
|
||||
"""Execute a docker container to start unit test dedicated"""
|
||||
if tests is None:
|
||||
unit_test = os.listdir(self.workspace())
|
||||
else:
|
||||
if type(tests) is not list:
|
||||
tests = [tests]
|
||||
unit_test = tests
|
||||
|
||||
try:
|
||||
for test in unit_test:
|
||||
print(f'Launch unit test "{test}"')
|
||||
cmds = self.base_command + [f'{PurePosixPath(self.container.local_folder, "tests", "unit_test", test)}']
|
||||
|
||||
docker_test = self.client.containers.run(self.container.ref, command=cmds,
|
||||
volumes=self.container.volumes, privileged=True,
|
||||
environment=self.container.environments, detach=True,
|
||||
name=test, tty=True)
|
||||
exit_docker = docker_test.wait()
|
||||
if exit_docker['StatusCode'] != 0:
|
||||
print(f'Container error "{exit_docker["StatusCode"]}".\n\t'
|
||||
f'Show log : \n\t'
|
||||
f'{docker_test.logs()}')
|
||||
raise ContainerErrorTest
|
||||
|
||||
except ContainerErrorTest as exception:
|
||||
print(f'{exception.__doc__}')
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,51 @@
|
||||
import os
|
||||
import requests
|
||||
|
||||
from pathlib import Path
|
||||
from github import Github
|
||||
|
||||
|
||||
def read_token():
|
||||
token_file = Path(os.getcwd(), 'tests', 'token.txt')
|
||||
if token_file.exists():
|
||||
with open(token_file, 'r') as f:
|
||||
token = f.read()
|
||||
|
||||
else:
|
||||
token = os.environ.get('token')
|
||||
|
||||
return token
|
||||
|
||||
|
||||
def get_release_file(filename: str, repo: str, prerelease: bool = False):
|
||||
"""Download from Github the latest release files"""
|
||||
g = Github(read_token())
|
||||
repository = g.get_repo(repo)
|
||||
all_releases = repository.get_releases()
|
||||
assets_release = None
|
||||
|
||||
for release in all_releases:
|
||||
print(f'Get "{filename}", with a prerelease value to "{prerelease}" ')
|
||||
if release.prerelease is prerelease:
|
||||
assets_release = release.get_assets()
|
||||
break
|
||||
else:
|
||||
assets_release = release.get_assets()
|
||||
break
|
||||
|
||||
for file in assets_release:
|
||||
if file.name == filename:
|
||||
dl_file(file.url, file.name)
|
||||
|
||||
|
||||
def dl_file(url: str, filename: str):
|
||||
"""From specific asset, download it"""
|
||||
headers = {
|
||||
'Authorization': f'token {read_token()}',
|
||||
'Accept': 'application/octet-stream'
|
||||
}
|
||||
session = requests.Session()
|
||||
link = session.get(url, stream=True, headers=headers)
|
||||
with open(filename, 'wb') as f:
|
||||
for chunk in link.iter_content(1024*1024):
|
||||
f.write(chunk)
|
||||
@@ -0,0 +1,12 @@
|
||||
class Archive(Exception):
|
||||
def __init__(self, source):
|
||||
self.source = source
|
||||
|
||||
|
||||
class ArchiveFolderSourceNotFound(Archive):
|
||||
def __str__(self):
|
||||
return f'Can\'t find the folder source "{self.source}".'
|
||||
|
||||
|
||||
class ContainerErrorTest(Exception):
|
||||
""" Failed to generate the test """
|
||||
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
from .issue import ArchiveFolderSourceNotFound
|
||||
|
||||
|
||||
def generate_archive(list_clean: list, name: str):
|
||||
"""From the plugin folder, generate an archive to test his code"""
|
||||
archive_path_source = pathlib.Path(os.getcwd(), name)
|
||||
archive_filename = pathlib.Path(os.getcwd(), f"{name}.zip")
|
||||
archive_file_generate = zipfile.ZipFile(archive_filename, 'w')
|
||||
|
||||
try:
|
||||
# Check if all default folder are present.
|
||||
if not archive_path_source.exists():
|
||||
raise ArchiveFolderSourceNotFound(source=name)
|
||||
|
||||
for directory, subdir, files in os.walk(archive_path_source):
|
||||
if '__pycache__' not in directory:
|
||||
for file in files:
|
||||
append_file = pathlib.Path(directory, file)
|
||||
included_file = pathlib.Path(os.path.relpath(append_file))
|
||||
archive_file_generate.write(append_file, included_file)
|
||||
|
||||
archive_file_generate.close()
|
||||
list_clean.append(archive_filename)
|
||||
|
||||
except BaseException as e:
|
||||
print(f'Generate Archive error : \n\t{e}')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def ordering_test_file():
|
||||
unit_test_folder = pathlib.Path(os.getcwd(), "tests", "unit_test")
|
||||
unit_test = os.listdir(unit_test_folder)
|
||||
unit_test_b3d = []
|
||||
unit_test_ue = []
|
||||
|
||||
for test in unit_test:
|
||||
if '_ue_' in test:
|
||||
unit_test_ue.append(test)
|
||||
elif '_b3d_' in test:
|
||||
unit_test_b3d.append(test)
|
||||
|
||||
return {'blender': unit_test_b3d, 'unreal': unit_test_ue}
|
||||
@@ -0,0 +1,69 @@
|
||||
import os
|
||||
|
||||
from pathlib import PurePosixPath
|
||||
|
||||
|
||||
class ContainerObject(object):
|
||||
"""All data request to launch one dedicated container"""
|
||||
|
||||
def __init__(self, name: str, image: str, tag: str = None):
|
||||
self.label: str = name.lower()
|
||||
self.image: str = image
|
||||
self.tag: str = self.set_tag(tag)
|
||||
self.local_folder: str = ''
|
||||
self.volumes: list[str] = self.set_volumes()
|
||||
self.environments: list[str] = self.set_environments()
|
||||
self.commands: list[str] = self.set_commands()
|
||||
self.ref = f'{self.image}:{self.tag}'
|
||||
|
||||
def set_tag(self, tag):
|
||||
"""Determine the tag use with the docker image call"""
|
||||
if 'blender' in self.label:
|
||||
if tag is not None:
|
||||
return tag
|
||||
else:
|
||||
return 'latest'
|
||||
elif 'unreal' in self.label:
|
||||
if tag is not None:
|
||||
return tag
|
||||
else:
|
||||
return 'dev-slim-4.27.2'
|
||||
else:
|
||||
return None
|
||||
|
||||
def set_volumes(self):
|
||||
"""Define folder/path with mount with each docker container"""
|
||||
if 'blender' in self.label:
|
||||
self.local_folder = '/addon_moderlab'
|
||||
elif 'unreal' in self.label:
|
||||
self.local_folder = '/project'
|
||||
else:
|
||||
return None
|
||||
return [f'{self.workspace()}:{self.local_folder}']
|
||||
|
||||
def set_environments(self):
|
||||
"""Add environment variable start with the docker container"""
|
||||
if 'blender' in self.label:
|
||||
return [f'FOLDER_TEST={self.local_folder}']
|
||||
else:
|
||||
return []
|
||||
|
||||
def set_commands(self):
|
||||
"""Write all commands execute on the docker container start"""
|
||||
cmds = ['/bin/sh']
|
||||
if 'blender' in self.label:
|
||||
cmds.append(str(PurePosixPath(self.local_folder, "tests", "launch_test_b3d.sh")))
|
||||
else:
|
||||
cmds.append(str(PurePosixPath(self.local_folder, "tests", "launch_test_ue.sh")))
|
||||
cmds.append(str(PurePosixPath(self.local_folder, "tests", "unreal_sample", "empty_project",
|
||||
"EmptyProject.uproject")))
|
||||
|
||||
return cmds
|
||||
|
||||
@staticmethod
|
||||
def workspace():
|
||||
"""Define the workspace folder"""
|
||||
if os.environ.get('GITHUB_WORKSPACE'):
|
||||
return os.environ['GITHUB_WORKSPACE']
|
||||
else:
|
||||
return os.getcwd()
|
||||
Reference in New Issue
Block a user