forked from kenmcmil/ivy
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathivy_command_mixin.py
More file actions
829 lines (700 loc) · 37 KB
/
Copy pathivy_command_mixin.py
File metadata and controls
829 lines (700 loc) · 37 KB
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
"""
Command generation mixin for PantherIvy service manager.
This mixin extracts command generation logic from the IvyCommandGenerator component
to follow standard PANTHER architecture patterns.
"""
import os
import re
from pathlib import Path
from typing import Any, Dict, List, Optional, Protocol, Union
from panther.core.command_processor.builders import ServiceCommandBuilder
from panther.core.command_processor.models.shell_command import ShellCommand
from panther.core.command_processor.utils import CommandUtils
from ._shared import oppose_role
class IvyCommandMixin:
"""
Mixin for Ivy-specific command generation.
This mixin consolidates all command generation logic from the IvyCommandGenerator
component into methods that can be directly used by the service manager.
"""
def __init__(self, *args, **kwargs):
"""Initialize IvyCommandMixin with ServiceCommandBuilder integration."""
super().__init__(*args, **kwargs)
self.command_builder = None
self._initialize_command_builder()
def _initialize_command_builder(self):
"""Initialize ServiceCommandBuilder if role is available."""
if not self.command_builder and hasattr(self, "role"):
role = getattr(self, "role", None)
if role:
self.command_builder = ServiceCommandBuilder(role)
else:
self.logger.warning(
"No role available for ServiceCommandBuilder initialization"
) if hasattr(self, "logger") else None
def generate_ivy_pre_compile_commands(self) -> List[Union[str, ShellCommand]]:
"""
Generate pre-compilation commands with IP resolution and environment setup.
Returns:
List of command strings
"""
commands = []
# Initialize environment file for logging -> use phase-based structure
commands.append(
"echo '# Ivy setup log' >> /app/logs/pre-compile/ivy_setup.log"
)
# Network resolution is handled by placeholders
target_info = ""
if hasattr(self, "service_targets") and self.service_targets:
target_info = f" (target: {self.service_targets})"
commands.append(
f'echo "Ivy service {self.service_name} using network-aware placeholder resolution{target_info}" >> /app/logs/pre-compile/ivy_setup.log'
)
# Clean build directory (safe operation - create directory if missing and clean)
use_system_models = getattr(
self.service_config_to_test.implementation, "use_system_models", False
)
env_protocol_path = self.get_protocol_model_path(use_system_models)
commands.append(
f"mkdir -p '{env_protocol_path}/build/' && find '{env_protocol_path}/build/' -maxdepth 1 -type f -delete 2>/dev/null || true"
)
return self.phase_command_processed(commands, "pre-compile")
def generate_ivy_compile_commands(self) -> List[Union[str, ShellCommand]]:
"""
Generate compilation commands including Ivy tool updates and test compilation.
Returns:
List of command strings
"""
commands = []
try:
# Emit command generation started event (if available)
if hasattr(self, "emit_command_generation_started"):
self.emit_command_generation_started("compile")
# Build comprehensive compilation commands
compilation_commands = self._generate_comprehensive_compilation_commands()
commands.extend(compilation_commands)
# Notify compilation started (if available)
if hasattr(self, "notify_service_event"):
protocol_name = self.get_protocol_name()
test_to_compile = getattr(self, "test_to_compile", "unknown_test")
self.notify_service_event(
"compilation_started",
{
"protocol": protocol_name,
"test": test_to_compile,
},
)
# Signal ivy compilation completion to coordination system
service_name = getattr(self, "service_name", "ivy")
ready_commands = [
f'echo "Ivy compilation completed for {service_name}" >> /app/logs/coordination.log',
f"touch /app/coordination/{service_name}_ivy_ready",
f'echo "ready_$(date +%s)" > /app/coordination/{service_name}_ivy_ready',
]
if compilation_commands:
commands.extend(ready_commands)
else:
commands = ready_commands
# Emit command generated event (if available)
if hasattr(self, "emit_command_generated"):
self.emit_command_generated("compile", str(commands))
return self.phase_command_processed(commands, "compile")
except Exception as e:
self.logger.error(f"Failed to generate compile commands: {e}")
raise
def generate_ivy_deployment_commands(self) -> str:
"""
Generate deployment command arguments for Ivy test execution.
Returns:
str: Command arguments for test execution
"""
try:
self.logger.debug(
f"Generating deployment commands for service: {getattr(self, 'service_name', 'unknown')}"
)
# Get role from service manager
role = getattr(self, "role", None)
if not role:
self.logger.warning("No role found in service manager")
raise ValueError("Role is required for deployment command generation")
# Get service configuration
service_config = getattr(self, "service_config_to_test", None)
if not service_config:
self.logger.warning("No service configuration found")
raise ValueError(
"Service configuration is required for deployment command generation"
)
# Get role-specific parameters
params = {}
role_name = role.name if hasattr(role, "name") else str(role)
self.logger.debug(f"Role name: {role_name}")
# First, get parameters from the version config 'parameters' section
if (
hasattr(service_config, "implementation")
and service_config.implementation.version_config
):
version_config = service_config.implementation.version_config
self.logger.debug(
f"Found version_config: {type(version_config)} - {version_config}"
)
# version_config is now a dictionary, extract parameters
if isinstance(version_config, dict) and "parameters" in version_config:
version_params = version_config["parameters"]
self.logger.debug(
f"Extracting version parameters: {version_params}"
)
else:
self.logger.warning(
f"No 'parameters' found in version_config: {version_config}"
)
version_params = {}
if isinstance(version_params, dict):
# If it's a dictionary
self.logger.debug("Processing version_params as dictionary")
for param_name, param_data in version_params.items():
self.logger.debug(
f"Processing param {param_name}: {param_data} (type: {type(param_data)})"
)
if isinstance(param_data, dict) and "value" in param_data:
params[param_name] = param_data["value"]
self.logger.debug(
f" -> Set {param_name} = {param_data['value']}"
)
else:
params[param_name] = param_data
self.logger.debug(f" -> Set {param_name} = {param_data}")
else:
self.logger.warning(
f"version_params is neither dict nor object with __dict__: {type(version_params)}"
)
else:
self.logger.warning(
"service_config.implementation.version_config not available for parameter extraction"
)
if hasattr(service_config, "implementation") and hasattr(
service_config.implementation, "version"
):
implem_version = service_config.implementation.version
self.logger.debug(f"Version is: {implem_version}")
# Get role-specific parameters from version_config
if hasattr(service_config, "implementation") and hasattr(
service_config.implementation, "version_config"
):
version_config = service_config.implementation.version_config
if role_name == "server":
# Check for server parameters in dict or object
server_params = None
if isinstance(version_config, dict) and "server" in version_config:
server_params = version_config["server"]
elif hasattr(version_config, "server"):
server_params = version_config.server
if server_params:
self.logger.debug(
"Using server parameters from implementation version"
)
self.logger.debug(f"Server parameters: {server_params}")
if isinstance(server_params, dict):
params |= server_params
elif hasattr(server_params, "__dict__"):
params |= server_params.__dict__
else:
self.logger.warning(
"No server parameters found in implementation version"
)
raise ValueError("No server parameters found")
elif role_name == "client":
# Check for client parameters in dict or object
client_params = None
if isinstance(version_config, dict) and "client" in version_config:
client_params = version_config["client"]
elif hasattr(version_config, "client"):
client_params = version_config.client
if client_params:
self.logger.debug(
"Using client parameters from implementation version"
)
self.logger.debug(f"Client parameters: {client_params}")
if isinstance(client_params, dict):
params |= client_params
elif hasattr(client_params, "__dict__"):
params |= client_params.__dict__
else:
self.logger.warning(
"No client parameters found in implementation version"
)
raise ValueError("No client parameters found")
# Add additional parameters from implementation
if hasattr(service_config, "implementation") and hasattr(
service_config.implementation, "parameters"
):
impl_params = service_config.implementation.parameters
if hasattr(impl_params, "__dict__"):
for param_name, param_obj in impl_params.__dict__.items():
if hasattr(param_obj, "value"):
params[param_name] = param_obj.value
else:
params[param_name] = param_obj
# Get service name -> this is critical and must not be None
service_name = getattr(self, "service_name", None)
if not service_name:
self.logger.error("Service name is not set in service manager")
raise ValueError(
"Service name is required for network placeholder resolution"
)
# Additional validation to ensure it's not None or empty string
if (
service_name == "None"
or service_name == ""
or not str(service_name).strip()
):
self.logger.error(
f"Invalid service name: '{service_name}' -> must be a valid service identifier"
)
raise ValueError(
f"Service name '{service_name}' is invalid for network placeholder resolution"
)
params["service_name"] = service_name
# Get target service name
target = None
# First check protocol configuration
if hasattr(service_config, "protocol") and service_config.protocol:
self.logger.debug(
f"Protocol config available: {service_config.protocol}"
)
if hasattr(service_config.protocol, "target"):
target = service_config.protocol.target
self.logger.info(f"Using target from protocol config: {target}")
# If no target from protocol, check service_targets
if not target:
target = getattr(self, "service_targets", None)
if target:
self.logger.info(f"Using target from service_targets: {target}")
# Validate target -> for ivy services, we need a target
if not target:
if role_name == "client":
self.logger.warning(
"No target service specified for network resolution"
)
raise ValueError(
"Target service must be specified for Ivy client role"
)
# Fallback to hardcoded defaults
if role_name == "server":
target = "ivy_server" # Default server service name
params["target"] = target
params["role"] = role_name
params["implementation"] = self.implementation_name
params["is_server"] = role_name == "server"
# Ivy role inversion: when testing a server IUT, Ivy acts as client.
# is_client means "Ivy acts as client in this test", NOT "the IUT is a client".
params["is_client"] = oppose_role(role_name) == "client"
params["test_name"] = self.test_to_compile
params["timeout_cmd"] = f"timeout {service_config.timeout} "
# Validate critical parameters before template rendering
critical_params = ["service_name", "target"]
for param_name in critical_params:
if param_name not in params:
self.logger.error(
f"Critical parameter '{param_name}' is missing from template params"
)
raise ValueError(
f"Parameter '{param_name}' is required for network placeholder resolution"
)
param_value = params[param_name]
if param_value is None:
self.logger.error(
f"Critical parameter '{param_name}' is None in template params"
)
raise ValueError(
f"Parameter '{param_name}' cannot be None for network placeholder resolution"
)
# Check for string representations of None or empty values
if str(param_value) in {"None", "", "null", "undefined"}:
self.logger.error(
f"Critical parameter '{param_name}' has invalid value: '{param_value}'"
)
raise ValueError(
f"Parameter '{param_name}' cannot be '{param_value}' for network placeholder resolution"
)
# Use template rendering to generate arguments
template_name = f"{oppose_role(role_name)}_command.jinja"
if template_renderer := getattr(self, "template_renderer", None):
# Preprocess template context to resolve network placeholders
if hasattr(self, "preprocess_template_context_with_network_resolution"):
params = self.preprocess_template_context_with_network_resolution(
params
)
self.logger.debug(
"Preprocessed template context with network resolution"
)
cmd_args = template_renderer.render_template(template_name, params)
# Validate and decode rendered command
if cmd_args:
is_valid, corrected_cmd_args = self._validate_deployment_command(
cmd_args
)
if is_valid:
self.logger.debug(
f"Generated command args from template: {corrected_cmd_args.strip()}"
)
return corrected_cmd_args.strip()
else:
self.logger.warning("Command arguments validation failed")
raise ValueError("Generated command arguments are invalid")
else:
self.logger.warning("No command arguments generated from template")
raise ValueError("Generated command arguments are empty")
else:
self.logger.warning("No template renderer available")
raise ValueError("No template renderer available")
except Exception as e:
self.logger.error(f"Failed to generate deployment commands: {e}")
raise
def generate_ivy_post_run_commands(self) -> List[Union[str, ShellCommand]]:
"""
Generate post-run cleanup commands.
Returns:
List of command strings
"""
commands = []
# Copy test binary if needed
if (
hasattr(self, "test_to_compile")
and self.test_to_compile
and hasattr(self, "env_protocol_model_path")
):
# Get build directory
tests_build_dir = self._get_build_dir()
test_path = os.path.join(
self.env_protocol_model_path, tests_build_dir, self.test_to_compile
)
commands.extend(
[
f"cp '{test_path}' '/app/logs/artifacts/{self.test_to_compile}'", # Use phase-based artifacts directory
f"find '{os.path.dirname(test_path)}' -name '{os.path.basename(test_path)}*' -type f -delete 2>/dev/null || true",
]
)
return self.phase_command_processed(commands, "post-run")
def phase_command_processed(self, commands, phase) -> List[str]:
"""
Process commands for the phase command and log the processed commands.
This method takes a list of commands and processes them, then logs the
structured command generation with the provided argument.
Args:
commands (list): List of commands to be processed.
phase: Argument to be passed to the structured command generation logger.
Returns:
list: The processed commands.
"""
processed_commands = self._process_commands(commands, phase)
CommandUtils.log_structured_command_generation(
self.logger, phase, processed_commands
)
return processed_commands
def _build_ivy_update_commands(self) -> List[str]:
"""Build Ivy tool update commands."""
commands = [
"echo 'Updating Ivy tool...' >> /app/logs/compile/ivy_setup.log",
"cd /opt/panther_ivy && sudo env PURE_PYTHON_BUILD=1 python3.10 -m pip install . >> /app/logs/compile/ivy_setup.log 2>&1",
"cd /opt/panther_ivy && if [ -f lib/libz3.so ]; then cp lib/libz3.so /opt/panther_ivy/ivy/z3/ && echo 'Copied libz3.so to ivy/z3/'; else echo 'No local libz3.so (z3_source=pip), skipping copy'; fi >> /app/logs/compile/ivy_setup.log 2>&1",
# Ensure target directories exist in the site-packages install
"mkdir -p \"$PYTHON_IVY_DIR/ivy/include/1.7\" \"$PYTHON_IVY_DIR/ivy/lib\" >> /app/logs/compile/ivy_setup.log 2>&1",
'echo "Copying updated Ivy files (from /opt/panther_ivy/ivy/include/1.7/) into $PYTHON_IVY_DIR/ivy/include/1.7/." >> /app/logs/compile/ivy_setup.log',
# Initialize copied files list for cleanup tracking
"echo '' > /app/logs/compile/copied_ivy_files.list",
"find '/opt/panther_ivy/ivy/include/1.7/' -type f -name '*.ivy' -exec echo {} ';' >> '/app/logs/compile/copied_ivy_files.list' 2>> /app/logs/compile/ivy_setup.log",
"find '/opt/panther_ivy/ivy/include/1.7/' -type f -name '*.ivy' -exec cp {} \"$PYTHON_IVY_DIR/ivy/include/1.7/\" ';' >> /app/logs/compile/ivy_setup.log 2>&1",
# Find and copy files while saving their destinations to the list
"echo 'Copied files saved to /app/logs/compile/copied_ivy_files.list for future cleanup' >> /app/logs/compile/ivy_setup.log",
]
# Protocol-specific setup
if self.get_protocol_name() in ["quic", "apt"]:
commands.extend(self._build_quic_setup_commands())
# Set up Ivy model
commands.extend(self._build_ivy_model_setup_commands())
return commands
def _build_quic_setup_commands(self) -> List[str]:
"""Build QUIC-specific setup commands."""
commands = [
"echo 'Copying QUIC libraries...' >> /app/logs/compile/ivy_setup.log",
"cp -f -a '/opt/picotls/'*.a $PYTHON_IVY_DIR/ivy/lib/ >> /app/logs/compile/ivy_setup.log 2>&1",
"cp -f -a '/opt/picotls/'*.a '/opt/panther_ivy/ivy/lib/' >> /app/logs/compile/ivy_setup.log 2>&1",
"cp -f '/opt/picotls/include/picotls.h' $PYTHON_IVY_DIR/ivy/include/picotls.h >> /app/logs/compile/ivy_setup.log 2>&1",
"cp -f '/opt/picotls/include/picotls.h' '/opt/panther_ivy/ivy/include/picotls.h' >> /app/logs/compile/ivy_setup.log 2>&1",
"cp -r -f '/opt/picotls/include/picotls/.' $PYTHON_IVY_DIR/ivy/include/picotls >> /app/logs/compile/ivy_setup.log 2>&1",
]
if hasattr(self, "env_protocol_model_path"):
# Add quic_ser_deser.h copy
use_system_models = getattr(
self.service_config_to_test.implementation, "use_system_models", False
)
if use_system_models:
quic_ser_deser_path = f"{self.env_protocol_model_path}/apt_protocols/quic/quic_utils/quic_ser_deser.h"
else:
quic_ser_deser_path = (
f"{self.env_protocol_model_path}/quic_utils/quic_ser_deser.h"
)
commands.append(
f"cp -f '{quic_ser_deser_path}' $PYTHON_IVY_DIR/ivy/include/1.7/ >> /app/logs/compile/ivy_setup.log 2>&1"
)
return commands
def _build_ivy_model_setup_commands(self) -> List[str]:
"""Build Ivy model setup commands."""
if not hasattr(self, "env_protocol_model_path"):
self.logger.warning(
"env_protocol_model_path is not set — skipping Ivy model setup commands"
)
return []
commands = [
"echo 'Setting up Ivy model...' >> /app/logs/compile/ivy_setup.log",
f"echo 'Updating include path from {self.env_protocol_model_path}' >> /app/logs/compile/ivy_setup.log",
"find '"
+ self.env_protocol_model_path
+ "' -type f -name '*.ivy' -exec echo {} ';' >> '/app/logs/compile/copied_ivy_files.list' 2>> /app/logs/compile/ivy_setup.log",
"find '"
+ self.env_protocol_model_path
+ "' -type f -name '*.ivy' -exec cp -f {} $PYTHON_IVY_DIR/ivy/include/1.7/ ';' >> /app/logs/compile/ivy_setup.log 2>&1",
"ls -l $PYTHON_IVY_DIR/ivy/include/1.7/ >> /app/logs/compile/ivy_setup.log",
]
return commands
def _build_test_compilation_commands(self):
"""
Build commands for test compilation using ivy_check.
Returns:
List[str]: Command sequence for test compilation
"""
# System (APT) models don't need test compilation
use_system_models = getattr(
self.service_config_to_test.implementation, "use_system_models", False
)
if use_system_models:
return []
container_base_path = self.env_protocol_model_path
# Get role information
role = self.role
role_name = role.name if hasattr(role, "name") else str(role)
protocol_name = self.get_protocol_name()
# Get internal iterations from PantherIvyConfig (set via YAML config)
service_config = getattr(self, "service_config_to_test", None)
internal_iterations = getattr(
service_config, "internal_iterations_per_test", 300
)
self.logger.debug(
f"Test compilation config: internal_iterations={internal_iterations}, "
f"role={role_name}, protocol={protocol_name}"
)
# Construct test directory path (use_system_models already returned early)
test_dir = self._extract_test_directory_from_name(
self.test_to_compile, role_name
)
container_file_path = os.path.join(
container_base_path, f"{protocol_name}_tests", test_dir
)
self.logger.info(f"Container path for test compilation: {container_file_path}")
# Get build directory
tests_build_dir = self._get_build_dir()
return [
f"echo 'Compiling test {self.test_to_compile} into {container_file_path}/{tests_build_dir}' >> /app/logs/compile/ivy_compile.log",
f"mkdir -p '{container_base_path}/{tests_build_dir}'",
f"cd $PYTHON_IVY_DIR/ivy/include/1.7 && pwd >> /app/logs/compile/ivy_compile.log 2>&1 && ls -la >> /app/logs/compile/ivy_compile.log 2>&1 && echo $PATH && ivyc show_compiled=false trace=false target=test test_iters={internal_iterations} {self.test_to_compile}.ivy >> /app/logs/compile/ivy_compile.log 2>&1",
"COMPILE_RESULT=$?",
'(if [ "$'
+ '{COMPILE_RESULT:-0}" -eq 0 ] 2>/dev/null; then echo "Compilation succeeded"; else echo "Compilation failed with code $'
+ '{COMPILE_RESULT:-unknown}"; fi) > /app/logs/compile/compilation_status.txt',
"echo 'Copying executable from ivy include to build directory...' >> /app/logs/compile/ivy_compile.log",
f"cp $PYTHON_IVY_DIR/ivy/include/1.7/{self.test_to_compile} {container_base_path}/{tests_build_dir}/ >> /app/logs/compile/ivy_compile.log 2>&1",
"echo 'Copying executable from ivy include to outputs directory...' >> /app/logs/compile/ivy_compile.log",
f"cp $PYTHON_IVY_DIR/ivy/include/1.7/{self.test_to_compile} /app/logs/compile/{self.test_to_compile} 2>&1",
f"cp $PYTHON_IVY_DIR/ivy/include/1.7/{self.test_to_compile}.cpp /app/logs/compile/{self.test_to_compile}.cpp 2>&1",
f"cp $PYTHON_IVY_DIR/ivy/include/1.7/{self.test_to_compile}.h /app/logs/compile/{self.test_to_compile}.h 2>&1",
f"ls -la {container_base_path}/{tests_build_dir}/ >> /app/logs/compile/ivy_compile.log",
]
def _extract_test_directory_from_name(self, test_name: str, role_name: str) -> str:
"""Extract test directory from test name."""
if "client" in test_name.lower():
return "client_tests"
elif "server" in test_name.lower():
return "server_tests"
else:
# Fallback to opposite role
return f"{oppose_role(role_name)}_tests"
def _get_build_dir(self) -> str:
"""Get build directory from configuration with robust extraction."""
service_config = getattr(self, "service_config_to_test", None)
if service_config and hasattr(service_config, "implementation"):
impl = service_config.implementation
# Path 1: Direct access to implementation parameters
if hasattr(impl, "parameters") and impl.parameters:
params = impl.parameters
if hasattr(params, "tests_build_dir"):
param_value = params.tests_build_dir
if hasattr(param_value, "value"):
return str(param_value.value)
return str(param_value)
# Path 2: Check version parameters
if hasattr(impl, "version") and hasattr(impl.version, "parameters"):
version_params = impl.version.parameters
if hasattr(version_params, "tests_build_dir"):
param_value = version_params.tests_build_dir
if hasattr(param_value, "value"):
return str(param_value.value)
return str(param_value)
# Path 3: Direct attribute check on implementation
if hasattr(impl, "tests_build_dir"):
param_value = impl.tests_build_dir
if hasattr(param_value, "value"):
return str(param_value.value)
return str(param_value)
return "build"
def _generate_comprehensive_compilation_commands(self) -> List[str]:
"""Generate comprehensive compilation commands."""
self.logger.debug(
f"Generating compilation commands for service: {getattr(self, 'service_name', 'unknown')}"
)
# Set up environments
service_config = getattr(self, "service_config_to_test", None)
if not service_config:
raise ValueError(
"service_config_to_test is not set — cannot generate compilation commands. "
"Aborting to prevent false 'ready' signal without actual compilation."
)
# Get environment configurations
protocol_env = {}
if hasattr(service_config, "implementation") and (
hasattr(service_config.implementation, "version")
and hasattr(service_config.implementation.version, "env")
):
protocol_env = service_config.implementation.version.env or {}
# Adjust protocol environment for non-system models
use_system_models = (
getattr(service_config.implementation, "use_system_models", False)
if hasattr(service_config, "implementation")
else False
)
if not use_system_models:
for key in protocol_env:
if isinstance(protocol_env[key], str):
protocol_env[key] = protocol_env[key].replace(
"/apt/apt_protocols", ""
)
# Set test path
test_to_compile = getattr(self, "test_to_compile", None)
self.logger.info(f"Setting test path to: {test_to_compile}")
# Build Ivy tool update commands
update_commands = self._build_ivy_update_commands()
# Build test compilation commands
test_commands = self._build_test_compilation_commands()
return update_commands + test_commands
def _process_commands(
self, commands: List[Union[str, ShellCommand]], phase: str
) -> List[Union[str, ShellCommand]]:
"""Process commands through ServiceCommandBuilder."""
# Ensure command builder is initialized
if not self.command_builder:
# Fallback if no command builder available
return [cmd.strip() for cmd in commands if cmd and cmd.strip()]
try:
# Reset builder to start fresh
self.command_builder.reset()
# Add commands to builder
for cmd in commands:
if cmd and isinstance(cmd, str):
# Determine if this is a non-critical command like ls or echo
is_non_critical_command = cmd.strip().startswith(("ls", "echo"))
# TODO: or "pre-compile" in phase ?
# A command is critical if we're in compile phase AND it's not a simple echo/ls command
is_critical = ("compile" in phase) and not is_non_critical_command
self.command_builder.add_command(
cmd.strip(),
is_critical=is_critical,
)
elif isinstance(cmd, ShellCommand):
# Determine if this is a non-critical command like ls or echo
is_non_critical_command = cmd.command.strip().startswith(
("ls", "echo")
)
# A command is critical if we're in compile/pre-compile phase AND it's not a simple echo/ls command
is_critical = ("compile" in phase) and not is_non_critical_command
cmd.metadata["is_critical"] = is_critical
self.command_builder.add_command(cmd.command.strip(), cmd.metadata)
# Process and return commands
processed = self.command_builder.process_commands("panther_ivy")
# Extract command strings from processed format
# The process_commands returns a list of dicts with command info
return processed
except Exception as e:
self.logger.error(
f"Command processing failed for phase '{phase}': {e}. "
f"Falling back to raw commands (error detection may be impaired).",
exc_info=True,
)
return [cmd.strip() for cmd in commands if cmd and cmd.strip()]
def _validate_deployment_command(self, cmd_args: str) -> tuple[bool, str]:
"""Validate deployment command for common issues.
Returns:
Tuple of (is_valid, corrected_command)
"""
validation_errors = []
self.logger.debug(f"Validating command arguments: {cmd_args}")
# Decode HTML entities first
import html
decoded_cmd_args = html.unescape(cmd_args)
if decoded_cmd_args != cmd_args:
self.logger.debug(f"Decoded HTML entities in command: {decoded_cmd_args}")
cmd_args = decoded_cmd_args
# Check for @None placeholders
if "@None" in cmd_args:
validation_errors.append("Contains @None placeholders")
# Check for unbalanced braces
if cmd_args.count("{") != cmd_args.count("}"):
validation_errors.append("Contains unbalanced braces")
# Check for empty parameter values
empty_params = re.findall(r"(\w+)=\s*(?=\s|\}|$)", cmd_args)
if empty_params:
validation_errors.append(f"Contains empty parameters: {empty_params}")
# Check for malformed placeholders - updated to handle service names with valid characters
valid_placeholder_pattern = (
r"@\{[a-zA-Z_][a-zA-Z0-9_]*:[a-zA-Z_][a-zA-Z0-9_]*:[a-zA-Z_][a-zA-Z0-9_]*\}"
)
all_placeholders = re.findall(r"@\{[^}]+\}", cmd_args)
if malformed := [
p for p in all_placeholders if not re.match(valid_placeholder_pattern, p)
]:
validation_errors.append(f"Contains malformed placeholders: {malformed}")
if validation_errors:
self.logger.error(
f"Command validation failed: {', '.join(validation_errors)}"
)
self.logger.error(f"Generated command: {cmd_args}")
return False, cmd_args
return True, cmd_args
def _validate_shell_syntax(self, cmd_args: str) -> List[str]:
"""Validate shell syntax for common errors."""
errors = []
# Check for malformed redirections
malformed_redirections = re.findall(r">\d+/", cmd_args)
if malformed_redirections:
errors.append(
f"Malformed redirections: {malformed_redirections} (should be '2>/dev/null' or '> /dev/null 2>&1')"
)
# Check for unmatched quotes
single_quotes = cmd_args.count("'")
double_quotes = cmd_args.count('"')
if single_quotes % 2 != 0:
errors.append("Unmatched single quotes")
if double_quotes % 2 != 0:
errors.append("Unmatched double quotes")
# Check for missing spaces in command chains
if re.search(r"[^;]\s*;\s*[^;]", cmd_args):
# This is actually correct, so let's check for missing spaces around operators
pass
# Check for invalid path patterns
invalid_paths = re.findall(r"/[^/\s]*[^/\s\w.-][^/\s]*", cmd_args)
# Filter out valid special characters in paths
actual_invalid = [p for p in invalid_paths if not re.match(r"^/[\w./-]*$", p)]
if actual_invalid:
errors.append(f"Potentially invalid paths: {actual_invalid}")
# Check for command substitution issues
if "`" in cmd_args and cmd_args.count("`") % 2 != 0:
errors.append("Unmatched backticks in command substitution")
return errors