unitytk 0.0.7__tar.gz → 0.0.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unitytk-0.0.7/unitytk.egg-info → unitytk-0.0.8}/PKG-INFO +2 -2
- {unitytk-0.0.7 → unitytk-0.0.8}/docs/README.md +1 -1
- unitytk-0.0.8/test/test_deploy_templates.py +412 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/__init__.py +3 -2
- unitytk-0.0.8/unitytk/template_deployer.py +628 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/templates/README.md +14 -4
- {unitytk-0.0.7 → unitytk-0.0.8/unitytk.egg-info}/PKG-INFO +2 -2
- unitytk-0.0.7/test/test_deploy_templates.py +0 -198
- unitytk-0.0.7/unitytk/template_deployer.py +0 -237
- {unitytk-0.0.7 → unitytk-0.0.8}/LICENSE +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/MANIFEST.in +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/pyproject.toml +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/setup.cfg +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_asset_delivery.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_audio_events_integration.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_bool_to_fade_curve.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_c130_fcr_integration.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_c5m_visibility.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_clip_name_contract_integration.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_controller_runtime_integration.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_emissive_groups_integration.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_fade_window_preservation.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_launcher.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_lightmap_metadata_integration.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_render_opacity_controller.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_render_opacity_e2e.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_render_opacity_integration.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_scene_builder.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_shadow_plane_integration.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_shared_material_standalone.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_shot_metadata_integration.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_shots_audio_sidebyside_integration.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/test/test_standalone_enduser.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/asset_delivery.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/launcher.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/scene_builder.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/templates/AudioEventController.cs +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/templates/EmissiveGroupController.cs +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/templates/EmissiveGroups.hlsl +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/templates/EmissiveGroupsLit.shader +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/templates/LightmapMetadataController.cs +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/templates/M3trik.Unitytk.asmdef +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/templates/RenderOpacityController.cs +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/templates/ShadowPlaneController.cs +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/templates/ShotMetadataController.cs +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/templates/UnitytkSettings.cs +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk/templates/__init__.py +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk.egg-info/SOURCES.txt +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk.egg-info/dependency_links.txt +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk.egg-info/requires.txt +0 -0
- {unitytk-0.0.7 → unitytk-0.0.8}/unitytk.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unitytk
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: A modular Python toolkit for Unity interaction.
|
|
5
5
|
Author-email: Ryan Simpson <m3trik@outlook.com>
|
|
6
6
|
License: MIT
|
|
@@ -15,7 +15,7 @@ License-File: LICENSE
|
|
|
15
15
|
Requires-Dist: pythontk>=0.7.71
|
|
16
16
|
Dynamic: license-file
|
|
17
17
|
|
|
18
|
-
[](../test/)
|
|
19
19
|
|
|
20
20
|
# unitytk
|
|
21
21
|
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
# !/usr/bin/python
|
|
2
|
+
# coding=utf-8
|
|
3
|
+
"""Tests for the embedded-UPM-package template deployment (no Unity needed).
|
|
4
|
+
|
|
5
|
+
The C# controller templates are compile-coupled: every controller calls the
|
|
6
|
+
shared ``ImportGate`` and ``CarrierImport`` classes that only
|
|
7
|
+
``UnitytkSettings.cs`` defines, so a Unity project must hold the *core* plus
|
|
8
|
+
whichever controllers it deploys to compile. These tests pin that contract on
|
|
9
|
+
the Python side — the only side that can be tested without a licensed Unity
|
|
10
|
+
install — including the per-component selection the DCC panels' script
|
|
11
|
+
checklist drives.
|
|
12
|
+
"""
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import shutil
|
|
16
|
+
import tempfile
|
|
17
|
+
import unittest
|
|
18
|
+
from unittest import mock
|
|
19
|
+
|
|
20
|
+
import unitytk
|
|
21
|
+
from unitytk import TemplateDeployer
|
|
22
|
+
from unitytk.template_deployer import UPM_PACKAGE_NAME
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DeployTemplateTest(unittest.TestCase):
|
|
26
|
+
def setUp(self):
|
|
27
|
+
self.tmp = tempfile.mkdtemp(prefix="unitytk_deploy_")
|
|
28
|
+
self.project = os.path.join(self.tmp, "Project")
|
|
29
|
+
os.makedirs(os.path.join(self.project, "Assets"))
|
|
30
|
+
self.package = os.path.join(self.project, "Packages", UPM_PACKAGE_NAME)
|
|
31
|
+
|
|
32
|
+
def tearDown(self):
|
|
33
|
+
shutil.rmtree(self.tmp, ignore_errors=True)
|
|
34
|
+
|
|
35
|
+
def test_single_template_round_trips(self):
|
|
36
|
+
dest = os.path.join(self.package, "UnitytkSettings.cs")
|
|
37
|
+
written = TemplateDeployer.deploy("UnitytkSettings.cs", dest)
|
|
38
|
+
self.assertTrue(os.path.isfile(written))
|
|
39
|
+
with open(written, encoding="utf-8") as fh:
|
|
40
|
+
self.assertIn("class ImportGate", fh.read())
|
|
41
|
+
|
|
42
|
+
def test_deploy_package_ships_the_full_coupled_set(self):
|
|
43
|
+
"""Every bundled controller, shader asset, the asmdef, the catalog
|
|
44
|
+
README and a generated package.json land — no partial deploy."""
|
|
45
|
+
written = TemplateDeployer.deploy_package(self.project)
|
|
46
|
+
names = sorted(os.path.basename(p) for p in written)
|
|
47
|
+
self.assertEqual(
|
|
48
|
+
names,
|
|
49
|
+
[
|
|
50
|
+
"AudioEventController.cs",
|
|
51
|
+
"EmissiveGroupController.cs",
|
|
52
|
+
"EmissiveGroups.hlsl",
|
|
53
|
+
"EmissiveGroupsLit.shader",
|
|
54
|
+
"LightmapMetadataController.cs",
|
|
55
|
+
"M3trik.Unitytk.asmdef",
|
|
56
|
+
"README.md",
|
|
57
|
+
"RenderOpacityController.cs",
|
|
58
|
+
"ShadowPlaneController.cs",
|
|
59
|
+
"ShotMetadataController.cs",
|
|
60
|
+
"UnitytkSettings.cs",
|
|
61
|
+
"package.json",
|
|
62
|
+
],
|
|
63
|
+
)
|
|
64
|
+
for p in written:
|
|
65
|
+
self.assertTrue(os.path.isfile(p))
|
|
66
|
+
self.assertGreater(os.path.getsize(p), 0)
|
|
67
|
+
self.assertTrue(
|
|
68
|
+
os.path.normcase(p).startswith(os.path.normcase(self.package)),
|
|
69
|
+
f"{p} escaped the package folder",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def test_package_manifest_matches_the_release(self):
|
|
73
|
+
"""package.json carries the UPM identity + this unitytk version, so
|
|
74
|
+
the Package Manager row is truthful and updates version-bump cleanly."""
|
|
75
|
+
TemplateDeployer.deploy_package(self.project)
|
|
76
|
+
with open(os.path.join(self.package, "package.json"), encoding="utf-8") as fh:
|
|
77
|
+
manifest = json.load(fh)
|
|
78
|
+
self.assertEqual(manifest["name"], UPM_PACKAGE_NAME)
|
|
79
|
+
self.assertEqual(manifest["version"], unitytk.__version__)
|
|
80
|
+
self.assertTrue(manifest["displayName"])
|
|
81
|
+
self.assertTrue(manifest["unity"])
|
|
82
|
+
|
|
83
|
+
def test_deploy_package_rejects_a_non_unity_folder(self):
|
|
84
|
+
"""Guards against scattering the package into a wrong path."""
|
|
85
|
+
with self.assertRaises(ValueError):
|
|
86
|
+
TemplateDeployer.deploy_package(self.tmp)
|
|
87
|
+
|
|
88
|
+
def test_shader_pair_travels_together(self):
|
|
89
|
+
"""``EmissiveGroupsLit.shader`` includes the .hlsl by RELATIVE path, so
|
|
90
|
+
deploying one without the other is a shader compile error in Unity."""
|
|
91
|
+
written = TemplateDeployer.deploy_package(self.project)
|
|
92
|
+
names = {os.path.basename(p) for p in written}
|
|
93
|
+
self.assertIn("EmissiveGroups.hlsl", names)
|
|
94
|
+
shader = os.path.join(self.package, "EmissiveGroupsLit.shader")
|
|
95
|
+
with open(shader, encoding="utf-8") as fh:
|
|
96
|
+
self.assertIn('#include "EmissiveGroups.hlsl"', fh.read())
|
|
97
|
+
|
|
98
|
+
# ---------------------------------------------------------------- components
|
|
99
|
+
def test_components_derive_from_the_bundle(self):
|
|
100
|
+
"""One required core plus one optional component per controller, keyed
|
|
101
|
+
and labelled off the filename — a controller added to the bundle
|
|
102
|
+
surfaces in the panels' checklist with no registry to update."""
|
|
103
|
+
comps = TemplateDeployer.components()
|
|
104
|
+
core = comps[0]
|
|
105
|
+
self.assertEqual(core.key, "core")
|
|
106
|
+
self.assertTrue(core.required)
|
|
107
|
+
self.assertIn("UnitytkSettings.cs", core.files)
|
|
108
|
+
self.assertIn("M3trik.Unitytk.asmdef", core.files)
|
|
109
|
+
# No controller hides in core (each is its own opt-in component).
|
|
110
|
+
self.assertFalse([n for n in core.files if n.endswith("Controller.cs")])
|
|
111
|
+
|
|
112
|
+
optional = {c.key: c for c in comps[1:]}
|
|
113
|
+
self.assertEqual(
|
|
114
|
+
sorted(optional),
|
|
115
|
+
[
|
|
116
|
+
"audio_event",
|
|
117
|
+
"emissive_group",
|
|
118
|
+
"lightmap_metadata",
|
|
119
|
+
"render_opacity",
|
|
120
|
+
"shadow_plane",
|
|
121
|
+
"shot_metadata",
|
|
122
|
+
],
|
|
123
|
+
)
|
|
124
|
+
self.assertEqual(optional["shot_metadata"].label, "Shot Metadata")
|
|
125
|
+
self.assertFalse(any(c.required for c in optional.values()))
|
|
126
|
+
# Every bundled file belongs to exactly one component.
|
|
127
|
+
owned = [n for c in comps for n in c.files]
|
|
128
|
+
self.assertEqual(sorted(owned), sorted(TemplateDeployer._bundle_names()))
|
|
129
|
+
self.assertEqual(len(owned), len(set(owned)))
|
|
130
|
+
|
|
131
|
+
def test_shader_pair_belongs_to_its_controller(self):
|
|
132
|
+
"""``EmissiveGroupsLit.shader`` includes the .hlsl by RELATIVE path, so
|
|
133
|
+
the pair travels with the controller that needs it — never on its own,
|
|
134
|
+
never left behind."""
|
|
135
|
+
emissive = {c.key: c for c in TemplateDeployer.components()}["emissive_group"]
|
|
136
|
+
self.assertEqual(
|
|
137
|
+
sorted(emissive.files),
|
|
138
|
+
[
|
|
139
|
+
"EmissiveGroupController.cs",
|
|
140
|
+
"EmissiveGroups.hlsl",
|
|
141
|
+
"EmissiveGroupsLit.shader",
|
|
142
|
+
],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def test_satellite_goes_to_the_longest_matching_controller(self):
|
|
146
|
+
"""Ownership is longest-stem-wins, so adding an `EmissiveController.cs`
|
|
147
|
+
beside `EmissiveGroupController.cs` can't give the shader pair two
|
|
148
|
+
owners -- a doubly-owned file deploys twice but survives the first
|
|
149
|
+
uninstall, desyncing deploy from remove."""
|
|
150
|
+
bundle = [
|
|
151
|
+
"EmissiveController.cs",
|
|
152
|
+
"EmissiveGroupController.cs",
|
|
153
|
+
"EmissiveGroups.hlsl",
|
|
154
|
+
"EmissiveGroupsLit.shader",
|
|
155
|
+
"UnitytkSettings.cs",
|
|
156
|
+
]
|
|
157
|
+
with mock.patch.object(TemplateDeployer, "_bundle_names", staticmethod(lambda: bundle)):
|
|
158
|
+
comps = {c.key: c for c in TemplateDeployer.components()}
|
|
159
|
+
self.assertEqual(comps["emissive"].files, ("EmissiveController.cs",))
|
|
160
|
+
self.assertEqual(
|
|
161
|
+
comps["emissive_group"].files,
|
|
162
|
+
(
|
|
163
|
+
"EmissiveGroupController.cs",
|
|
164
|
+
"EmissiveGroups.hlsl",
|
|
165
|
+
"EmissiveGroupsLit.shader",
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
owned = [n for c in comps.values() for n in c.files]
|
|
169
|
+
self.assertEqual(len(owned), len(set(owned)), "a file has two owners")
|
|
170
|
+
|
|
171
|
+
def test_keys_and_labels_survive_an_acronym(self):
|
|
172
|
+
"""Names are split on real word boundaries, so a `UVLayoutController`
|
|
173
|
+
reads as 'UV Layout' / `uv_layout` rather than 'U V Layout'."""
|
|
174
|
+
self.assertEqual(TemplateDeployer._component_key("UVLayout"), "uv_layout")
|
|
175
|
+
self.assertEqual(TemplateDeployer._component_label("UVLayout"), "UV Layout")
|
|
176
|
+
self.assertEqual(TemplateDeployer._component_key("ShotMetadata"), "shot_metadata")
|
|
177
|
+
|
|
178
|
+
def test_selected_deploy_carries_core_and_nothing_else(self):
|
|
179
|
+
"""A checked subset deploys those components + the core they compile
|
|
180
|
+
against, and leaves the unchecked ones out of the project."""
|
|
181
|
+
written = TemplateDeployer.deploy_package(self.project, ["audio_event"])
|
|
182
|
+
names = sorted(os.path.basename(p) for p in written)
|
|
183
|
+
self.assertEqual(
|
|
184
|
+
names,
|
|
185
|
+
[
|
|
186
|
+
"AudioEventController.cs",
|
|
187
|
+
"M3trik.Unitytk.asmdef",
|
|
188
|
+
"README.md",
|
|
189
|
+
"UnitytkSettings.cs",
|
|
190
|
+
"package.json",
|
|
191
|
+
],
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def test_selected_deploy_is_additive(self):
|
|
195
|
+
"""Deploying a second component updates in place — it never removes the
|
|
196
|
+
one already there (Install / Update, not Sync)."""
|
|
197
|
+
TemplateDeployer.deploy_package(self.project, ["audio_event"])
|
|
198
|
+
TemplateDeployer.deploy_package(self.project, ["shot_metadata"])
|
|
199
|
+
present = set(os.listdir(self.package))
|
|
200
|
+
self.assertIn("AudioEventController.cs", present)
|
|
201
|
+
self.assertIn("ShotMetadataController.cs", present)
|
|
202
|
+
|
|
203
|
+
def test_unknown_component_raises_before_writing(self):
|
|
204
|
+
"""A typo must not silently deploy a smaller set than asked for."""
|
|
205
|
+
with self.assertRaises(ValueError):
|
|
206
|
+
TemplateDeployer.deploy_package(self.project, ["audio_events"])
|
|
207
|
+
self.assertFalse(os.path.isdir(self.package))
|
|
208
|
+
|
|
209
|
+
def test_selective_uninstall_keeps_the_rest(self):
|
|
210
|
+
TemplateDeployer.deploy_package(self.project)
|
|
211
|
+
removed = TemplateDeployer.uninstall_package(self.project, ["emissive_group"])
|
|
212
|
+
self.assertEqual(
|
|
213
|
+
sorted(os.path.basename(p) for p in removed),
|
|
214
|
+
[
|
|
215
|
+
"EmissiveGroupController.cs",
|
|
216
|
+
"EmissiveGroups.hlsl",
|
|
217
|
+
"EmissiveGroupsLit.shader",
|
|
218
|
+
],
|
|
219
|
+
)
|
|
220
|
+
present = set(os.listdir(self.package))
|
|
221
|
+
self.assertNotIn("EmissiveGroupController.cs", present)
|
|
222
|
+
self.assertIn("AudioEventController.cs", present)
|
|
223
|
+
self.assertIn("UnitytkSettings.cs", present)
|
|
224
|
+
|
|
225
|
+
def test_removing_the_last_component_takes_the_package(self):
|
|
226
|
+
"""Core alone gates nothing — the folder goes with the last controller
|
|
227
|
+
rather than leaving a settings page for no channels."""
|
|
228
|
+
TemplateDeployer.deploy_package(self.project, ["audio_event"])
|
|
229
|
+
TemplateDeployer.uninstall_package(self.project, ["audio_event"])
|
|
230
|
+
self.assertFalse(os.path.isdir(self.package))
|
|
231
|
+
|
|
232
|
+
def test_core_cannot_be_uninstalled_on_its_own(self):
|
|
233
|
+
TemplateDeployer.deploy_package(self.project)
|
|
234
|
+
with self.assertRaises(ValueError):
|
|
235
|
+
TemplateDeployer.uninstall_package(self.project, ["core"])
|
|
236
|
+
self.assertTrue(os.path.isfile(os.path.join(self.package, "UnitytkSettings.cs")))
|
|
237
|
+
|
|
238
|
+
def test_status_reports_not_installed_then_up_to_date(self):
|
|
239
|
+
"""The panels' Status action: absent → installed=False with the full
|
|
240
|
+
bundle missing; after deploy → current version, nothing missing."""
|
|
241
|
+
before = TemplateDeployer.status(self.project)
|
|
242
|
+
self.assertFalse(before["installed"])
|
|
243
|
+
self.assertFalse(before["up_to_date"])
|
|
244
|
+
self.assertIn("UnitytkSettings.cs", before["missing"])
|
|
245
|
+
|
|
246
|
+
TemplateDeployer.deploy_package(self.project)
|
|
247
|
+
after = TemplateDeployer.status(self.project)
|
|
248
|
+
self.assertTrue(after["installed"])
|
|
249
|
+
self.assertEqual(after["version"], unitytk.__version__)
|
|
250
|
+
self.assertEqual(after["missing"], [])
|
|
251
|
+
self.assertTrue(after["up_to_date"])
|
|
252
|
+
|
|
253
|
+
def test_status_flags_a_partial_deploy(self):
|
|
254
|
+
"""A deleted file shows in ``missing`` + ``incomplete`` and clears
|
|
255
|
+
``up_to_date`` — a component is only valid whole."""
|
|
256
|
+
TemplateDeployer.deploy_package(self.project)
|
|
257
|
+
os.remove(os.path.join(self.package, "UnitytkSettings.cs"))
|
|
258
|
+
info = TemplateDeployer.status(self.project)
|
|
259
|
+
self.assertEqual(info["missing"], ["UnitytkSettings.cs"])
|
|
260
|
+
self.assertEqual(info["incomplete"], ["UnitytkSettings.cs"])
|
|
261
|
+
self.assertFalse(info["up_to_date"])
|
|
262
|
+
|
|
263
|
+
def test_status_reports_each_component(self):
|
|
264
|
+
"""The panels' checklist report: deployed components tick, opted-out
|
|
265
|
+
ones don't, and opting out is NOT a broken install."""
|
|
266
|
+
TemplateDeployer.deploy_package(self.project, ["audio_event"])
|
|
267
|
+
info = TemplateDeployer.status(self.project)
|
|
268
|
+
states = {c["key"]: c["installed"] for c in info["components"]}
|
|
269
|
+
self.assertTrue(states["core"])
|
|
270
|
+
self.assertTrue(states["audio_event"])
|
|
271
|
+
self.assertFalse(states["shot_metadata"])
|
|
272
|
+
# Not deploying a channel is a choice, not a partial deploy.
|
|
273
|
+
self.assertEqual(info["incomplete"], [])
|
|
274
|
+
self.assertTrue(info["up_to_date"])
|
|
275
|
+
self.assertIn("ShotMetadataController.cs", info["missing"])
|
|
276
|
+
|
|
277
|
+
def test_status_flags_a_controller_with_no_core(self):
|
|
278
|
+
"""A controller left without the ImportGate it compiles against is
|
|
279
|
+
broken even though each file set is individually 'whole'."""
|
|
280
|
+
TemplateDeployer.deploy_package(self.project, ["audio_event"])
|
|
281
|
+
for name in ("UnitytkSettings.cs", "M3trik.Unitytk.asmdef", "README.md"):
|
|
282
|
+
os.remove(os.path.join(self.package, name))
|
|
283
|
+
info = TemplateDeployer.status(self.project)
|
|
284
|
+
self.assertIn("UnitytkSettings.cs", info["incomplete"])
|
|
285
|
+
self.assertFalse(info["up_to_date"])
|
|
286
|
+
|
|
287
|
+
# ---------------------------------------------------------------- run_action
|
|
288
|
+
def test_run_action_status_lists_every_component(self):
|
|
289
|
+
"""One shared report for the Maya / Blender / standalone panels: a
|
|
290
|
+
header line plus a ticked row per component."""
|
|
291
|
+
TemplateDeployer.deploy_package(self.project, ["audio_event"])
|
|
292
|
+
lines = TemplateDeployer.run_action(self.project, "status")
|
|
293
|
+
self.assertTrue(all(level == "info" for level, _ in lines))
|
|
294
|
+
text = "\n".join(m for _, m in lines)
|
|
295
|
+
self.assertIn(f"deployed v{unitytk.__version__}", text)
|
|
296
|
+
self.assertIn("[x] Audio Event", text)
|
|
297
|
+
self.assertIn("[ ] Shot Metadata", text)
|
|
298
|
+
|
|
299
|
+
def test_run_action_install_and_uninstall_the_checked_set(self):
|
|
300
|
+
install = TemplateDeployer.run_action(
|
|
301
|
+
self.project, "install", ["audio_event"]
|
|
302
|
+
)
|
|
303
|
+
self.assertEqual([lvl for lvl, _ in install], ["info"])
|
|
304
|
+
self.assertIn("Audio Event", install[0][1])
|
|
305
|
+
self.assertTrue(
|
|
306
|
+
os.path.isfile(os.path.join(self.package, "AudioEventController.cs"))
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
uninstall = TemplateDeployer.run_action(
|
|
310
|
+
self.project, "uninstall", ["audio_event"]
|
|
311
|
+
)
|
|
312
|
+
self.assertEqual([lvl for lvl, _ in uninstall], ["info"])
|
|
313
|
+
self.assertFalse(os.path.isdir(self.package))
|
|
314
|
+
|
|
315
|
+
def test_run_action_rejects_an_empty_selection(self):
|
|
316
|
+
"""Nothing checked is a mistake to report, not a silent no-op (while
|
|
317
|
+
``None`` still means the full set)."""
|
|
318
|
+
for action in ("install", "uninstall"):
|
|
319
|
+
lines = TemplateDeployer.run_action(self.project, action, [])
|
|
320
|
+
self.assertEqual([lvl for lvl, _ in lines], ["error"])
|
|
321
|
+
self.assertFalse(os.path.isdir(self.package))
|
|
322
|
+
|
|
323
|
+
def test_run_action_reports_failures_instead_of_raising(self):
|
|
324
|
+
"""The panels log what came back; a bad key or action must not escape
|
|
325
|
+
as an exception into the DCC's event loop."""
|
|
326
|
+
for lines in (
|
|
327
|
+
TemplateDeployer.run_action(self.project, "install", ["nope"]),
|
|
328
|
+
TemplateDeployer.run_action(self.project, "frobnicate"),
|
|
329
|
+
TemplateDeployer.run_action(self.tmp, "install"), # not a Unity project
|
|
330
|
+
):
|
|
331
|
+
self.assertEqual([lvl for lvl, _ in lines], ["error"])
|
|
332
|
+
|
|
333
|
+
def test_uninstall_removes_only_our_package(self):
|
|
334
|
+
"""Uninstall deletes the deployed folder (True), no-ops when absent
|
|
335
|
+
(False), and REFUSES a folder whose package.json is not ours — never
|
|
336
|
+
delete a directory we merely expected to own."""
|
|
337
|
+
self.assertFalse(TemplateDeployer.uninstall_package(self.project))
|
|
338
|
+
|
|
339
|
+
TemplateDeployer.deploy_package(self.project)
|
|
340
|
+
self.assertTrue(TemplateDeployer.uninstall_package(self.project))
|
|
341
|
+
self.assertFalse(os.path.isdir(self.package))
|
|
342
|
+
|
|
343
|
+
os.makedirs(self.package)
|
|
344
|
+
with open(os.path.join(self.package, "package.json"), "w") as fh:
|
|
345
|
+
json.dump({"name": "com.example.other"}, fh)
|
|
346
|
+
with self.assertRaises(ValueError):
|
|
347
|
+
TemplateDeployer.uninstall_package(self.project)
|
|
348
|
+
self.assertTrue(os.path.isdir(self.package), "foreign folder untouched")
|
|
349
|
+
|
|
350
|
+
def test_deploy_package_never_writes_project_config(self):
|
|
351
|
+
"""Per-project settings live in ProjectSettings/UnitytkSettings.json,
|
|
352
|
+
owned by the project — deploying must not create or clobber them."""
|
|
353
|
+
TemplateDeployer.deploy_package(self.project)
|
|
354
|
+
self.assertFalse(
|
|
355
|
+
os.path.isfile(
|
|
356
|
+
os.path.join(self.project, "ProjectSettings", "UnitytkSettings.json")
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Every shared class lives in UnitytkSettings.cs; the other controllers
|
|
361
|
+
# must reference it (definer set / referencer set per class). Shader
|
|
362
|
+
# assets (.hlsl/.shader) carry no C# and are excluded.
|
|
363
|
+
COUPLED_DEFINER = {"UnitytkSettings.cs"}
|
|
364
|
+
COUPLED_REFERENCERS = {
|
|
365
|
+
"AudioEventController.cs",
|
|
366
|
+
"EmissiveGroupController.cs",
|
|
367
|
+
"LightmapMetadataController.cs",
|
|
368
|
+
"RenderOpacityController.cs",
|
|
369
|
+
"ShadowPlaneController.cs",
|
|
370
|
+
"ShotMetadataController.cs",
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
def _coupling_surface(self, shared_class):
|
|
374
|
+
"""Deploy the set; return ``(definers, referencers)`` of *shared_class*
|
|
375
|
+
across the C# templates only."""
|
|
376
|
+
written = [
|
|
377
|
+
p for p in TemplateDeployer.deploy_package(self.project)
|
|
378
|
+
if p.endswith(".cs")
|
|
379
|
+
]
|
|
380
|
+
definers, referencers = set(), set()
|
|
381
|
+
for p in written:
|
|
382
|
+
with open(p, encoding="utf-8") as fh:
|
|
383
|
+
text = fh.read()
|
|
384
|
+
name = os.path.basename(p)
|
|
385
|
+
if f"class {shared_class}" in text:
|
|
386
|
+
definers.add(name)
|
|
387
|
+
elif shared_class in text:
|
|
388
|
+
referencers.add(name)
|
|
389
|
+
return definers, referencers
|
|
390
|
+
|
|
391
|
+
def test_exactly_one_import_gate_definition_in_the_set(self):
|
|
392
|
+
"""One template defines ``ImportGate``; every other one references it.
|
|
393
|
+
|
|
394
|
+
Guards the compile-coupling as templates evolve: a second definition
|
|
395
|
+
would be a duplicate-class CS error, a dropped reference would mean a
|
|
396
|
+
controller stopped honoring the channel toggles + FBX allowlist.
|
|
397
|
+
"""
|
|
398
|
+
definers, referencers = self._coupling_surface("ImportGate")
|
|
399
|
+
self.assertEqual(definers, self.COUPLED_DEFINER)
|
|
400
|
+
self.assertEqual(referencers, self.COUPLED_REFERENCERS)
|
|
401
|
+
|
|
402
|
+
def test_exactly_one_carrier_import_definition_in_the_set(self):
|
|
403
|
+
"""One template defines ``CarrierImport``; every other one references
|
|
404
|
+
it — same coupling contract as ``ImportGate`` (both live in
|
|
405
|
+
``UnitytkSettings.cs``)."""
|
|
406
|
+
definers, referencers = self._coupling_surface("CarrierImport")
|
|
407
|
+
self.assertEqual(definers, self.COUPLED_DEFINER)
|
|
408
|
+
self.assertEqual(referencers, self.COUPLED_REFERENCERS)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
if __name__ == "__main__":
|
|
412
|
+
unittest.main(verbosity=2)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
__version__ = "0.0.
|
|
1
|
+
__version__ = "0.0.8"
|
|
2
2
|
|
|
3
3
|
from .launcher import UnityLauncher, UnityFinder
|
|
4
4
|
from .scene_builder import SceneBuilder
|
|
5
|
-
from .template_deployer import TemplateDeployer
|
|
5
|
+
from .template_deployer import TemplateDeployer, PackageComponent
|
|
6
6
|
from .asset_delivery import CopyToAssetsDeliverer, FileToUnityBridge
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
@@ -10,6 +10,7 @@ __all__ = [
|
|
|
10
10
|
"UnityFinder",
|
|
11
11
|
"SceneBuilder",
|
|
12
12
|
"TemplateDeployer",
|
|
13
|
+
"PackageComponent",
|
|
13
14
|
"CopyToAssetsDeliverer",
|
|
14
15
|
"FileToUnityBridge",
|
|
15
16
|
]
|