mirror of
https://github.com/JamesTheGiblet/BuddAI.git
synced 2026-01-08 21:58:40 +00:00
Add unit tests for analytics, fallback client, and refactored validators
- Implemented comprehensive unit tests for the BuddAI Analytics module, covering fallback statistics calculations. - Created tests for the FallbackClient to ensure proper escalation to various AI models and handling of missing API keys. - Developed unit tests for the refactored validator system, validating various hardware and coding standards. - Established a base validator interface and implemented specific validators for ESP32, Arduino, motor control, memory safety, and more. - Enhanced the validator registry to auto-discover and manage validators effectively. - Included detailed validation logic for common issues in embedded systems programming, such as unused variables, safety timeouts, and coding style violations.
This commit is contained in:
parent
99ef8f5592
commit
d4e09f6d13
43 changed files with 5036 additions and 622 deletions
143
validators/style_guide.py
Normal file
143
validators/style_guide.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import re
|
||||
from . import BaseValidator
|
||||
|
||||
class StyleValidator(BaseValidator):
|
||||
def refactor_loop_to_function(self, code: str) -> str:
|
||||
loop_match = re.search(r'void\s+loop\s*\(\s*\)\s*\{', code)
|
||||
if not loop_match: return code
|
||||
|
||||
start_idx = loop_match.end()
|
||||
brace_count = 1
|
||||
loop_body_end = -1
|
||||
|
||||
for i, char in enumerate(code[start_idx:], start=start_idx):
|
||||
if char == '{': brace_count += 1
|
||||
elif char == '}': brace_count -= 1
|
||||
|
||||
if brace_count == 0:
|
||||
loop_body_end = i
|
||||
break
|
||||
|
||||
if loop_body_end == -1: return code
|
||||
|
||||
body = code[start_idx:loop_body_end]
|
||||
new_code = code[:start_idx] + "\n runSystemLogic();\n" + code[loop_body_end:]
|
||||
new_code += "\n\nvoid runSystemLogic() {" + body + "}\n"
|
||||
return new_code
|
||||
|
||||
def validate(self, code: str, hardware: str, user_message: str) -> list[dict]:
|
||||
issues = []
|
||||
|
||||
# Check 11: Feature Bloat (Unrequested Button)
|
||||
if user_message:
|
||||
msg_lower = user_message.lower()
|
||||
if not any(w in msg_lower for w in ['button', 'switch', 'input', 'trigger']):
|
||||
for match in re.finditer(r'(?:int|bool|byte)\s+(\w*(?:button|btn|switch)\w*)\s*=\s*digitalRead\s*\([^;]+;', code, re.IGNORECASE):
|
||||
issues.append({
|
||||
"severity": "error",
|
||||
"line": self.find_line(code, match.group(0)),
|
||||
"message": f"Feature Bloat: Unrequested button code detected ('{match.group(1)}').",
|
||||
"fix": lambda c, m=match.group(0): c.replace(m, "")
|
||||
})
|
||||
|
||||
for match in re.finditer(r'digitalRead\s*\(\s*(\w*(?:BUTTON|BTN|SWITCH)\w*)\s*\)', code, re.IGNORECASE):
|
||||
issues.append({
|
||||
"severity": "error",
|
||||
"line": self.find_line(code, match.group(0)),
|
||||
"message": f"Feature Bloat: Unrequested button check detected ('{match.group(1)}').",
|
||||
"fix": lambda c, m=match.group(0): c.replace(m, "0")
|
||||
})
|
||||
|
||||
for match in re.finditer(r'pinMode\s*\(\s*\w+\s*,\s*INPUT(?:_PULLUP)?\s*\);', code):
|
||||
issues.append({
|
||||
"severity": "error",
|
||||
"line": self.find_line(code, match.group(0)),
|
||||
"message": "Feature Bloat: Unrequested input pin configuration.",
|
||||
"fix": lambda c, m=match.group(0): c.replace(m, "")
|
||||
})
|
||||
|
||||
for match in re.finditer(r'(?:int|bool|byte)\s+(\w*(?:button|btn|switch)\w*)\s*=\s*(?:LOW|HIGH|0|1|false|true)\s*;', code, re.IGNORECASE):
|
||||
issues.append({
|
||||
"severity": "error",
|
||||
"line": self.find_line(code, match.group(0)),
|
||||
"message": f"Feature Bloat: Unused button variable '{match.group(1)}'.",
|
||||
"fix": lambda c, m=match.group(0): c.replace(m, "")
|
||||
})
|
||||
|
||||
# Check 15: Function Naming Conventions
|
||||
func_defs = re.finditer(r'\b(void|int|bool|float|double|String|char|long|unsigned(?:\s+long)?)\s+([a-zA-Z0-9_]+)\s*\(', code)
|
||||
for match in func_defs:
|
||||
func_name = match.group(2)
|
||||
if func_name in ['setup', 'loop', 'main']: continue
|
||||
|
||||
if not re.match(r'^[a-z][a-zA-Z0-9]*$', func_name):
|
||||
suggestion = func_name
|
||||
if '_' in func_name:
|
||||
components = func_name.split('_')
|
||||
suggestion = components[0].lower() + ''.join(x.title() for x in components[1:])
|
||||
elif func_name[0].isupper():
|
||||
suggestion = func_name[0].lower() + func_name[1:]
|
||||
|
||||
issues.append({
|
||||
"severity": "warning",
|
||||
"line": self.find_line(code, match.group(0)),
|
||||
"message": f"Style: Function '{func_name}' should be camelCase (e.g., '{suggestion}').",
|
||||
"fix": lambda c, old=func_name, new=suggestion: c.replace(old, new)
|
||||
})
|
||||
|
||||
# Check 16: Monolithic Code Structure
|
||||
if "function" in user_message.lower() or "naming" in user_message.lower() or "modular" in user_message.lower():
|
||||
has_custom_funcs = False
|
||||
for match in re.finditer(r'\b(void|int|bool|float|double|String|char|long|unsigned(?:\s+long)?)\s+([a-zA-Z0-9_]+)\s*\(', code):
|
||||
if match.group(2) not in ['setup', 'loop', 'main']:
|
||||
has_custom_funcs = True
|
||||
break
|
||||
|
||||
if not has_custom_funcs:
|
||||
issues.append({
|
||||
"severity": "error",
|
||||
"message": "Structure Violation: Request asked for functions but code is monolithic.",
|
||||
"fix": lambda c: c.replace("void loop() {", "void loop() {\n runSystemLogic();\n}\n\nvoid runSystemLogic() {") + "\n}"
|
||||
})
|
||||
|
||||
# Check 17: Loop Length
|
||||
if "function" in user_message.lower() or "naming" in user_message.lower() or "modular" in user_message.lower():
|
||||
loop_match = re.search(r'void\s+loop\s*\(\s*\)\s*\{', code)
|
||||
if loop_match:
|
||||
start_idx = loop_match.end()
|
||||
brace_count = 1
|
||||
loop_body = ""
|
||||
for char in code[start_idx:]:
|
||||
if char == '{': brace_count += 1
|
||||
elif char == '}': brace_count -= 1
|
||||
if brace_count == 0: break
|
||||
loop_body += char
|
||||
|
||||
lines = [line.strip() for line in loop_body.split('\n')]
|
||||
significant_lines = [l for l in lines if l and not l.startswith('//') and not l.startswith('/*') and l != '']
|
||||
|
||||
if len(significant_lines) >= 10:
|
||||
issues.append({
|
||||
"severity": "error",
|
||||
"message": f"Modularity Violation: loop() has {len(significant_lines)} lines (limit 10). Move logic to functions.",
|
||||
"fix": lambda c: self.refactor_loop_to_function(c)
|
||||
})
|
||||
|
||||
# Check 21: Status LED Pattern
|
||||
if "status" in user_message.lower() and ("led" in user_message.lower() or "indicator" in user_message.lower()):
|
||||
breathing_match = re.search(r'(?:dutyCycle|brightness)\s*(\+=|\+\+|\-=|\-\-)', code)
|
||||
if breathing_match:
|
||||
issues.append({
|
||||
"severity": "error",
|
||||
"line": self.find_line(code, breathing_match.group(0)),
|
||||
"message": "Wrong Pattern: Status indicators should use Blink Patterns (States), not Breathing/Fading.",
|
||||
"fix": lambda c: c + "\n// [Fix Required] Implement setStatusLED(LEDStatus state) instead of fading."
|
||||
})
|
||||
|
||||
if not re.search(r'enum\s+(?:StatusState|LEDStatus)\s*\{', code):
|
||||
issues.append({
|
||||
"severity": "error",
|
||||
"message": "Missing Status Enum: Status LEDs require a state machine (enum LEDStatus {OFF, IDLE, ACTIVE, ERROR}).",
|
||||
"fix": lambda c: c.replace("void setup", "\n// [AUTO-FIX] Status Enum\nenum LEDStatus { OFF, IDLE, ACTIVE, ERROR };\nLEDStatus currentStatus = IDLE;\nunsigned long lastBlink = 0;\n\nvoid setup") if "void setup" in c else "// [AUTO-FIX] Status Enum\nenum LEDStatus { OFF, IDLE, ACTIVE, ERROR };\nLEDStatus currentStatus = IDLE;\nunsigned long lastBlink = 0;\n" + c
|
||||
})
|
||||
return issues
|
||||
Loading…
Add table
Add a link
Reference in a new issue