feat: add C/C++ language detection and dependency resolution with Makefile generation
Add comprehensive C and C++ project analysis including header classification (stdlib, POSIX, external), compiler flag suggestions, and Makefile generation. Extend DependencyResolver with C library package mappings for debian, fedora, arch, and brew platforms. Update ProjectAnalyzer with LANGUAGE_EXTENSIONS and BUILD_FILES mappings, rename python_version to language_version, and add build_system and compiler_flags fields to AnalysisResult. Enhance SafeCommandExecutor with incomplete argument detection for find, grep, and sed commands. Add metadata field to OperationResult in TransactionalFileSystem and fix hidden directory validation logic. Bump version to 1.69.0 and promote development status to Production/Stable.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import pytest
|
||||
from rp.core.project_analyzer import ProjectAnalyzer, AnalysisResult
|
||||
|
||||
@@ -22,6 +24,7 @@ class User(BaseModel):
|
||||
)
|
||||
|
||||
assert isinstance(result, AnalysisResult)
|
||||
assert result.language == 'python'
|
||||
assert 'requests' in result.dependencies
|
||||
assert 'pydantic' in result.dependencies
|
||||
|
||||
@@ -78,7 +81,7 @@ if (x := 10) > 5:
|
||||
code_content=code_with_walrus,
|
||||
)
|
||||
|
||||
version_parts = result.python_version.split('.')
|
||||
version_parts = result.language_version.split('.')
|
||||
assert int(version_parts[1]) >= 8
|
||||
|
||||
def test_directory_structure_planning(self):
|
||||
@@ -154,3 +157,300 @@ from fastapi.middleware.gzip import GZIPMiddleware
|
||||
|
||||
assert not result.valid
|
||||
assert any('GZIPMiddleware' in str(e) or 'fastapi' in str(e).lower() for e in result.errors)
|
||||
|
||||
|
||||
class TestCLanguageAnalyzer:
|
||||
def setup_method(self):
|
||||
self.analyzer = ProjectAnalyzer()
|
||||
|
||||
def test_detect_c_language(self):
|
||||
c_code = """
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
printf("Hello, World!\\n");
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
result = self.analyzer.analyze_requirements(
|
||||
spec_file="main.c",
|
||||
code_content=c_code,
|
||||
)
|
||||
|
||||
assert result.language == 'c'
|
||||
|
||||
def test_c_standard_headers_detection(self):
|
||||
c_code = """
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
result = self.analyzer.analyze_requirements(
|
||||
spec_file="main.c",
|
||||
code_content=c_code,
|
||||
)
|
||||
|
||||
assert 'stdio.h' in result.dependencies
|
||||
assert result.dependencies['stdio.h'] == 'stdlib'
|
||||
assert result.dependencies['math.h'] == 'stdlib'
|
||||
|
||||
def test_c_posix_headers_detection(self):
|
||||
c_code = """
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
result = self.analyzer.analyze_requirements(
|
||||
spec_file="main.c",
|
||||
code_content=c_code,
|
||||
)
|
||||
|
||||
assert 'unistd.h' in result.dependencies
|
||||
assert result.dependencies['unistd.h'] == 'posix'
|
||||
assert any('POSIX' in w for w in result.warnings)
|
||||
|
||||
def test_c_local_headers_detection(self):
|
||||
c_code = """
|
||||
#include <stdio.h>
|
||||
#include "myheader.h"
|
||||
#include "utils/helper.h"
|
||||
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
result = self.analyzer.analyze_requirements(
|
||||
spec_file="main.c",
|
||||
code_content=c_code,
|
||||
)
|
||||
|
||||
assert 'myheader.h' in result.dependencies
|
||||
assert result.dependencies['myheader.h'] == 'local'
|
||||
|
||||
def test_c_external_library_headers(self):
|
||||
c_code = """
|
||||
#include <stdio.h>
|
||||
#include <curl/curl.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
result = self.analyzer.analyze_requirements(
|
||||
spec_file="main.c",
|
||||
code_content=c_code,
|
||||
)
|
||||
|
||||
assert 'curl/curl.h' in result.dependencies
|
||||
assert result.dependencies['curl/curl.h'] == 'curl'
|
||||
assert any('curl' in w for w in result.warnings)
|
||||
|
||||
def test_c_standard_detection_c99(self):
|
||||
c_code = """
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
printf("%d\\n", i);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
result = self.analyzer.analyze_requirements(
|
||||
spec_file="main.c",
|
||||
code_content=c_code,
|
||||
)
|
||||
|
||||
assert result.language_version == 'c99'
|
||||
|
||||
def test_c_standard_detection_c11(self):
|
||||
c_code = """
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
int main() {
|
||||
_Static_assert(sizeof(int) >= 4, "int must be at least 4 bytes");
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
result = self.analyzer.analyze_requirements(
|
||||
spec_file="main.c",
|
||||
code_content=c_code,
|
||||
)
|
||||
|
||||
assert result.language_version == 'c11'
|
||||
|
||||
def test_c_standard_detection_gnu(self):
|
||||
c_code = """
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
typeof(5) x = 10;
|
||||
printf("%d\\n", x);
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
result = self.analyzer.analyze_requirements(
|
||||
spec_file="main.c",
|
||||
code_content=c_code,
|
||||
)
|
||||
|
||||
assert 'gnu' in result.language_version
|
||||
|
||||
def test_c_compiler_flags_suggestion(self):
|
||||
c_code = """
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
|
||||
int main() {
|
||||
pthread_t thread;
|
||||
double x = sqrt(2.0);
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
result = self.analyzer.analyze_requirements(
|
||||
spec_file="main.c",
|
||||
code_content=c_code,
|
||||
)
|
||||
|
||||
assert '-lm' in result.compiler_flags
|
||||
assert '-pthread' in result.compiler_flags
|
||||
assert any('-std=' in f for f in result.compiler_flags)
|
||||
assert '-Wall' in result.compiler_flags
|
||||
|
||||
def test_c_valid_analysis_no_errors(self):
|
||||
c_code = """
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello\\n");
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
result = self.analyzer.analyze_requirements(
|
||||
spec_file="main.c",
|
||||
code_content=c_code,
|
||||
)
|
||||
|
||||
assert result.valid
|
||||
assert len(result.errors) == 0
|
||||
|
||||
def test_c_shell_commands_validation(self):
|
||||
c_code = """
|
||||
#include <stdio.h>
|
||||
int main() { return 0; }
|
||||
"""
|
||||
commands = [
|
||||
"gcc -o main main.c",
|
||||
"make clean",
|
||||
"./main",
|
||||
]
|
||||
|
||||
result = self.analyzer.analyze_requirements(
|
||||
spec_file="main.c",
|
||||
code_content=c_code,
|
||||
commands=commands,
|
||||
)
|
||||
|
||||
valid_commands = [c for c in result.shell_commands if c['valid']]
|
||||
assert len(valid_commands) == 3
|
||||
|
||||
|
||||
class TestLanguageDetection:
|
||||
def setup_method(self):
|
||||
self.analyzer = ProjectAnalyzer()
|
||||
|
||||
def test_detect_python_from_content(self):
|
||||
python_code = """
|
||||
def hello():
|
||||
print("Hello, World!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
hello()
|
||||
"""
|
||||
lang = self.analyzer.detect_language(python_code)
|
||||
assert lang == 'python'
|
||||
|
||||
def test_detect_c_from_content(self):
|
||||
c_code = """
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
printf("Hello\\n");
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
lang = self.analyzer.detect_language(c_code)
|
||||
assert lang == 'c'
|
||||
|
||||
def test_detect_cpp_from_content(self):
|
||||
cpp_code = """
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Hello" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
lang = self.analyzer.detect_language(cpp_code)
|
||||
assert lang == 'cpp'
|
||||
|
||||
def test_detect_rust_from_content(self):
|
||||
rust_code = """
|
||||
fn main() {
|
||||
let x = 5;
|
||||
println!("x = {}", x);
|
||||
}
|
||||
"""
|
||||
lang = self.analyzer.detect_language(rust_code)
|
||||
assert lang == 'rust'
|
||||
|
||||
def test_detect_go_from_content(self):
|
||||
go_code = """
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello")
|
||||
}
|
||||
"""
|
||||
lang = self.analyzer.detect_language(go_code)
|
||||
assert lang == 'go'
|
||||
|
||||
def test_detect_javascript_from_content(self):
|
||||
js_code = """
|
||||
const express = require('express');
|
||||
|
||||
function hello() {
|
||||
console.log("Hello");
|
||||
}
|
||||
|
||||
export default hello;
|
||||
"""
|
||||
lang = self.analyzer.detect_language(js_code)
|
||||
assert lang == 'javascript'
|
||||
|
||||
def test_detect_language_from_file_extension(self):
|
||||
lang = self.analyzer.detect_language("", "main.c")
|
||||
assert lang == 'c'
|
||||
|
||||
lang = self.analyzer.detect_language("", "app.py")
|
||||
assert lang == 'python'
|
||||
|
||||
def test_detect_unknown_language(self):
|
||||
weird_content = "some random text without any language patterns"
|
||||
lang = self.analyzer.detect_language(weird_content)
|
||||
assert lang == 'unknown'
|
||||
|
||||
Reference in New Issue
Block a user