已合并
feat(aclrtc): 支持 ACLNN Kernel 常量化编译 #5103
feat(aclrtc): 支持 ACLNN Kernel 常量化编译 #5103
已合并
XuebinYang创建于 26 天前
24 个文件变更+5407-39
@@ -16,10 +16,10 @@ set(ACLRTC_TEST_HEADERS_FILES
16 16 
17file(GLOB ACLRTC_SRC_FILES17file(GLOB ACLRTC_SRC_FILES
18 ${ASCENDC_DIR}/tools/aclrtc/*.cpp18 ${ASCENDC_DIR}/tools/aclrtc/*.cpp
19+ ${ASCENDC_DIR}/tools/aclrtc/specialization/*.cpp
19)20)
20 21 
21-set(ACLRTC_RESOURCE_REGISTRY_SRC_FILES22+set(ACLRTC_RESOURCE_REGISTRY_DEPENDENCY_SRC_FILES
22- ${ASCENDC_DIR}/tools/aclrtc/specialization/resource_registry.cpp
23 ${ASCENDC_DIR}/tools/build/common/file_utils.cpp23 ${ASCENDC_DIR}/tools/build/common/file_utils.cpp
24)24)
25 25 
@@ -31,7 +31,7 @@ file(GLOB TEST_ACLRTC_SRC_FILES
31 31 
32add_executable(ascendc_ut_aclrtc32add_executable(ascendc_ut_aclrtc
33 ${ACLRTC_SRC_FILES}33 ${ACLRTC_SRC_FILES}
34- ${ACLRTC_RESOURCE_REGISTRY_SRC_FILES}34+ ${ACLRTC_RESOURCE_REGISTRY_DEPENDENCY_SRC_FILES}
35 ${TEST_ACLRTC_SRC_FILES}35 ${TEST_ACLRTC_SRC_FILES}
36)36)
37 37 
@@ -42,6 +42,7 @@ target_compile_definitions(ascendc_ut_aclrtc PRIVATE
42 42 
43target_include_directories(ascendc_ut_aclrtc PRIVATE43target_include_directories(ascendc_ut_aclrtc PRIVATE
44 ${ACLRTC_TEST_HEADERS_FILES}44 ${ACLRTC_TEST_HEADERS_FILES}
45+ ${ASCENDC_DIR}/tools/aclrtc
45 ${ASCENDC_DIR}/tools/aclrtc/specialization46 ${ASCENDC_DIR}/tools/aclrtc/specialization
46 ${ASCENDC_DIR}/tools/build/common47 ${ASCENDC_DIR}/tools/build/common
47 ${ASCEND_CANN_PACKAGE_PATH}/include48 ${ASCEND_CANN_PACKAGE_PATH}/include
@@ -0,0 +1,350 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "specialization/compilation_plan_executor.h"
12+#include "specialization/kernel_specialization_diagnostics.h"
13+ 
14+#include <gtest/gtest.h>
15+#include <mockcpp/mockcpp.hpp>
16+ 
17+#include <chrono>
18+#include <boost/filesystem.hpp>
19+#include <boost/system/error_code.hpp>
20+#include <fstream>
21+#include <poll.h>
22+#include <thread>
23+#include <sys/resource.h>
24+#include <sys/wait.h>
25+#include <time.h>
26+#include <type_traits>
27+#include <unistd.h>
28+ 
29+namespace {
30+namespace fs = boost::filesystem;
31+using namespace std::chrono_literals;
32+using ascendc::aclrtc::CompilationCommand;
33+using ascendc::aclrtc::CompilationCommandExecutionLimits;
34+using ascendc::aclrtc::CompilationCommandKind;
35+using ascendc::aclrtc::CompilationPlanExecutor;
36+using ascendc::aclrtc::CompilationProcessResult;
37+using ascendc::aclrtc::CompilationProcessTermination;
38+using ascendc::aclrtc::CompilationVariant;
39+using ascendc::aclrtc::KernelCompilationPlan;
40+using ascendc::aclrtc::KernelSpecializationDiagnostics;
41+using namespace mockcpp;
42+ 
43+int FailCommandOutputPoll(pollfd*, nfds_t, int)
44+{
45+ errno = EIO;
46+ return -1;
47+}
48+ 
49+int FailCommandLeaderInspection(idtype_t, id_t, siginfo_t*, int)
50+{
51+ errno = ECHILD;
52+ return -1;
53+}
54+ 
55+static_assert(
56+ std::is_same<std::underlying_type_t<CompilationVariant>, uint32_t>::value,
57+ "CompilationVariant must have a stable width");
58+static_assert(
59+ std::is_same<std::underlying_type_t<CompilationCommandKind>, uint32_t>::value,
60+ "CompilationCommandKind must have a stable width");
61+static_assert(
62+ std::is_same<std::underlying_type_t<CompilationProcessTermination>, uint32_t>::value,
63+ "CompilationProcessTermination must have a stable width");
64+static_assert(
65+ std::is_same<decltype(CompilationProcessResult::terminationCode), int32_t>::value,
66+ "terminationCode must have a stable width");
67+static_assert(
68+ std::is_same<decltype(CompilationCommandExecutionLimits::capturedOutputByteLimit), uint64_t>::value,
69+ "capturedOutputByteLimit must have a stable width");
70+ 
71+CompilationCommand CreateCommand(
72+ CompilationCommandKind commandKind, const char* executable, std::vector<std::string> arguments)
73+{
74+ return {commandKind, 0U, "test-command", executable, std::move(arguments)};
75+}
76+ 
77+class ScopedDiagnosticCurrentDirectory final {
78+public:
79+ ScopedDiagnosticCurrentDirectory()
80+ : previousDirectoryPath_(fs::current_path()),
81+ temporaryDirectoryPath_(fs::temp_directory_path() / fs::unique_path("aclrtc_executor_diagnostics_%%%%-%%%%"))
82+ {
83+ fs::create_directories(temporaryDirectoryPath_);
84+ fs::current_path(temporaryDirectoryPath_);
85+ }
86+ 
87+ ~ScopedDiagnosticCurrentDirectory()
88+ {
89+ boost::system::error_code ignoredError;
90+ fs::current_path(previousDirectoryPath_, ignoredError);
91+ fs::remove_all(temporaryDirectoryPath_, ignoredError);
92+ }
93+ 
94+private:
95+ fs::path previousDirectoryPath_;
96+ fs::path temporaryDirectoryPath_;
97+};
98+ 
99+std::string ReadTextFile(const fs::path& filePath)
100+{
101+ std::ifstream inputStream(filePath.string());
102+ return {std::istreambuf_iterator<char>(inputStream), std::istreambuf_iterator<char>()};
103+}
104+ 
105+TEST(CompilationPlanExecutorTest, ProductionLimitsMatchReviewedPerCommandTimeouts)
106+{
107+ const CompilationCommandExecutionLimits limits = CompilationCommandExecutionLimits::ProductionDefaults();
108+ EXPECT_EQ(limits.compileCommandTimeout, 5min);
109+ EXPECT_EQ(limits.objectCopyAndLinkCommandTimeout, 1min);
110+ EXPECT_EQ(limits.terminationGracePeriod, 5s);
111+ EXPECT_EQ(limits.capturedOutputByteLimit, 4U * 1024U * 1024U);
112+}
113+ 
114+TEST(CompilationPlanExecutorTest, ExecutesCommandsWithoutShellParsing)
115+{
116+ boost::system::error_code ignoredError;
117+ fs::remove("/tmp/aclrtc_should_not_exist", ignoredError);
118+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_success");
119+ CompilationPlanExecutor executor(diagnostics, CompilationCommandExecutionLimits::ProductionDefaults());
120+ KernelCompilationPlan plan;
121+ plan.compilationCommands.push_back(
122+ CreateCommand(CompilationCommandKind::Compile, "/bin/echo", {"$(touch /tmp/aclrtc_should_not_exist);literal"}));
123+ 
124+ EXPECT_EQ(executor.ExecuteCompilationPlan(plan), 0);
125+ EXPECT_FALSE(fs::exists("/tmp/aclrtc_should_not_exist"));
126+}
127+ 
128+TEST(CompilationPlanExecutorTest, RejectsEmbeddedNullBeforeSpawningProcess)
129+{
130+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_embedded_null");
131+ CompilationPlanExecutor executor(diagnostics, CompilationCommandExecutionLimits::ProductionDefaults());
132+ 
133+ std::string executableWithEmbeddedNull("/bin/true");
134+ executableWithEmbeddedNull.push_back('\0');
135+ executableWithEmbeddedNull += "/bin/false";
136+ KernelCompilationPlan executablePlan;
137+ CompilationCommand executableCommand = CreateCommand(CompilationCommandKind::Compile, "/bin/true", {});
138+ executableCommand.executablePath = fs::path(executableWithEmbeddedNull);
139+ executablePlan.compilationCommands.push_back(std::move(executableCommand));
140+ EXPECT_EQ(executor.ExecuteCompilationPlan(executablePlan), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
141+ 
142+ std::string argumentWithEmbeddedNull("accepted-prefix");
143+ argumentWithEmbeddedNull.push_back('\0');
144+ argumentWithEmbeddedNull += "discarded-suffix";
145+ KernelCompilationPlan argumentPlan;
146+ argumentPlan.compilationCommands.push_back(
147+ CreateCommand(CompilationCommandKind::Compile, "/bin/true", {argumentWithEmbeddedNull}));
148+ EXPECT_EQ(executor.ExecuteCompilationPlan(argumentPlan), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
149+}
150+ 
151+TEST(CompilationPlanExecutorTest, MapsCompileAndLinkFailuresToDifferentErrors)
152+{
153+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_failure");
154+ CompilationPlanExecutor executor(diagnostics, CompilationCommandExecutionLimits::ProductionDefaults());
155+ KernelCompilationPlan plan;
156+ plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/bin/false", {}));
157+ EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_ERROR_COMPILATION);
158+ 
159+ plan.compilationCommands[0].commandKind = CompilationCommandKind::Link;
160+ EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_ERROR_LINKING);
161+}
162+ 
163+TEST(CompilationPlanExecutorTest, UsesInjectedShortTimeoutInUnitTests)
164+{
165+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_timeout");
166+ CompilationCommandExecutionLimits limits;
167+ limits.compileCommandTimeout = 20ms;
168+ limits.objectCopyAndLinkCommandTimeout = 20ms;
169+ limits.terminationGracePeriod = 20ms;
170+ limits.capturedOutputByteLimit = 1024U;
171+ CompilationPlanExecutor executor(diagnostics, limits);
172+ KernelCompilationPlan plan;
173+ plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/bin/sleep", {"10"}));
174+ 
175+ const auto startedAt = std::chrono::steady_clock::now();
176+ EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_ERROR_COMPILATION);
177+ EXPECT_LT(std::chrono::steady_clock::now() - startedAt, 2s);
178+}
179+ 
180+TEST(CompilationPlanExecutorTest, EnforcesTimeoutWhileCommandContinuouslyProducesOutput)
181+{
182+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_continuous_output");
183+ CompilationCommandExecutionLimits limits{20ms, 20ms, 20ms, 1024U};
184+ CompilationPlanExecutor executor(diagnostics, limits);
185+ KernelCompilationPlan plan;
186+ plan.compilationCommands.push_back(CreateCommand(
187+ CompilationCommandKind::Compile, "/bin/sh", {"-c", "yes & yes & yes & yes & yes & yes & yes & yes & wait"}));
188+ 
189+ const auto startedAt = std::chrono::steady_clock::now();
190+ EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_ERROR_COMPILATION);
191+ EXPECT_LT(std::chrono::steady_clock::now() - startedAt, 2s);
192+}
193+ 
194+TEST(CompilationPlanExecutorTest, WaitsWithoutBusyPollingAfterCommandOutputCloses)
195+{
196+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_closed_output");
197+ CompilationCommandExecutionLimits limits{1s, 1s, 20ms, 1024U};
198+ CompilationPlanExecutor executor(diagnostics, limits);
199+ KernelCompilationPlan plan;
200+ plan.compilationCommands.push_back(
201+ CreateCommand(CompilationCommandKind::Compile, "/bin/sh", {"-c", "exec >/dev/null 2>&1; sleep 0.25"}));
202+ 
203+ timespec cpuStartedAt{};
204+ timespec cpuFinishedAt{};
205+ ASSERT_EQ(clock_gettime(CLOCK_THREAD_CPUTIME_ID, &cpuStartedAt), 0);
206+ EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_SUCCESS);
207+ ASSERT_EQ(clock_gettime(CLOCK_THREAD_CPUTIME_ID, &cpuFinishedAt), 0);
208+ const int64_t consumedCpuNanoseconds =
209+ (static_cast<int64_t>(cpuFinishedAt.tv_sec) - static_cast<int64_t>(cpuStartedAt.tv_sec)) * 1000000000LL +
210+ static_cast<int64_t>(cpuFinishedAt.tv_nsec) - static_cast<int64_t>(cpuStartedAt.tv_nsec);
211+ EXPECT_LT(consumedCpuNanoseconds, 100000000LL);
212+}
213+ 
214+TEST(CompilationPlanExecutorTest, WaitsForOutputPipeClosureAfterCommandLeaderExits)
215+{
216+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_leader_exit");
217+ CompilationCommandExecutionLimits limits{500ms, 500ms, 20ms, 1024U};
218+ CompilationPlanExecutor executor(diagnostics, limits);
219+ KernelCompilationPlan plan;
220+ plan.compilationCommands.push_back(
221+ CreateCommand(CompilationCommandKind::Compile, "/bin/sh", {"-c", "sleep 0.3 & exec /bin/true"}));
222+ 
223+ const auto startedAt = std::chrono::steady_clock::now();
224+ EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_SUCCESS);
225+ EXPECT_GE(std::chrono::steady_clock::now() - startedAt, 200ms);
226+}
227+ 
228+TEST(CompilationPlanExecutorTest, TimeoutTerminatesDescendantsAfterCommandLeaderExits)
229+{
230+ static uint64_t sequence = 0U;
231+ const fs::path descendantMarkerPath =
232+ fs::temp_directory_path() /
233+ ("aclrtc_descendant_marker_" + std::to_string(getpid()) + "_" + std::to_string(sequence++));
234+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_process_group");
235+ CompilationCommandExecutionLimits limits{20ms, 20ms, 20ms, 1024U};
236+ CompilationPlanExecutor executor(diagnostics, limits);
237+ KernelCompilationPlan plan;
238+ const std::string commandScript =
239+ "(trap '' TERM; count=0; while [ $count -lt 100 ]; do printf x >> '" + descendantMarkerPath.string() +
240+ "'; count=$((count + 1)); sleep 0.01; done; rm -f '" + descendantMarkerPath.string() + "') & exec /bin/true";
241+ plan.compilationCommands.push_back(
242+ CreateCommand(CompilationCommandKind::Compile, "/bin/sh", {"-c", commandScript}));
243+ 
244+ ASSERT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_ERROR_COMPILATION);
245+ // An orphaned descendant can remain as a zombie until the container init reaps it, so do not use kill(pid, 0)
246+ // as the assertion. A heartbeat that stops growing after the timeout directly verifies that the descendant did
247+ // not continue.
248+ const uintmax_t markerSizeAfterTimeout =
249+ fs::exists(descendantMarkerPath) ? fs::file_size(descendantMarkerPath) : 0U;
250+ std::this_thread::sleep_for(100ms);
251+ const uintmax_t markerSizeAfterWait = fs::exists(descendantMarkerPath) ? fs::file_size(descendantMarkerPath) : 0U;
252+ EXPECT_EQ(markerSizeAfterWait, markerSizeAfterTimeout);
253+ boost::system::error_code ignoredError;
254+ fs::remove(descendantMarkerPath, ignoredError);
255+}
256+ 
257+TEST(CompilationPlanExecutorTest, RecordsSpawnFailureAsInfrastructureFailure)
258+{
259+ ScopedDiagnosticCurrentDirectory diagnosticCurrentDirectory;
260+ const std::string specializationSessionId = "executor_spawn_failure";
261+ KernelSpecializationDiagnostics diagnostics(fs::current_path(), specializationSessionId);
262+ CompilationPlanExecutor executor(diagnostics, CompilationCommandExecutionLimits::ProductionDefaults());
263+ KernelCompilationPlan plan;
264+ plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/path/that/does/not/exist", {}));
265+ EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
266+ 
267+ const std::string compilationLog = ReadTextFile(fs::current_path() / "aclrtc_compile.log");
268+ EXPECT_NE(compilationLog.find("termination=infrastructure_failure"), std::string::npos);
269+ EXPECT_EQ(compilationLog.find("termination=exited code=0"), std::string::npos);
270+}
271+ 
272+TEST(CompilationPlanExecutorTest, BoundsCapturedProcessOutput)
273+{
274+ ScopedDiagnosticCurrentDirectory diagnosticCurrentDirectory;
275+ const std::string specializationSessionId = "executor_output_limit";
276+ KernelSpecializationDiagnostics diagnostics(fs::current_path(), specializationSessionId);
277+ CompilationCommandExecutionLimits limits = CompilationCommandExecutionLimits::ProductionDefaults();
278+ limits.capturedOutputByteLimit = 8U;
279+ CompilationPlanExecutor executor(diagnostics, limits);
280+ KernelCompilationPlan plan;
281+ plan.compilationCommands.push_back(
282+ CreateCommand(CompilationCommandKind::Compile, "/usr/bin/printf", {"0123456789abcdef"}));
283+ EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_SUCCESS);
284+ 
285+ const std::string compilationLog = ReadTextFile(fs::current_path() / "aclrtc_compile.log");
286+ EXPECT_NE(compilationLog.find("output_truncated=true"), std::string::npos);
287+ EXPECT_NE(compilationLog.find("01234567"), std::string::npos);
288+ EXPECT_EQ(compilationLog.find("89abcdef"), std::string::npos);
289+}
290+ 
291+TEST(CompilationPlanExecutorTest, ReportsToolProcessTerminatedBySignal)
292+{
293+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_signaled");
294+ CompilationPlanExecutor executor(diagnostics, CompilationCommandExecutionLimits::ProductionDefaults());
295+ KernelCompilationPlan plan;
296+ plan.compilationCommands.push_back(
297+ CreateCommand(CompilationCommandKind::Compile, "/bin/sh", {"-c", "kill -TERM $$"}));
298+ 
299+ EXPECT_EQ(executor.ExecuteCompilationPlan(plan), ascendc::aclrtc::ACLRTC_ERROR_COMPILATION);
300+}
301+ 
302+TEST(CompilationPlanExecutorTest, ReportsPipeCreationFailureWhenFileDescriptorsAreExhausted)
303+{
304+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_pipe_failure");
305+ CompilationPlanExecutor executor(diagnostics, CompilationCommandExecutionLimits::ProductionDefaults());
306+ KernelCompilationPlan plan;
307+ plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/bin/true", {}));
308+ 
309+ struct rlimit originalFileDescriptorLimit {};
310+ ASSERT_EQ(getrlimit(RLIMIT_NOFILE, &originalFileDescriptorLimit), 0);
311+ struct rlimit exhaustedFileDescriptorLimit = originalFileDescriptorLimit;
312+ exhaustedFileDescriptorLimit.rlim_cur = 0U;
313+ ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &exhaustedFileDescriptorLimit), 0);
314+ const aclError executionResult = executor.ExecuteCompilationPlan(plan);
315+ const int restoreLimitResult = setrlimit(RLIMIT_NOFILE, &originalFileDescriptorLimit);
316+ 
317+ ASSERT_EQ(restoreLimitResult, 0);
318+ EXPECT_EQ(executionResult, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
319+}
320+ 
321+TEST(CompilationPlanExecutorTest, ReportsCommandOutputPollFailure)
322+{
323+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_poll_failure");
324+ CompilationCommandExecutionLimits limits{1s, 1s, 20ms, 1024U};
325+ CompilationPlanExecutor executor(diagnostics, limits);
326+ KernelCompilationPlan plan;
327+ plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/bin/sleep", {"1"}));
328+ MOCKER(poll, int (*)(pollfd*, nfds_t, int)).expects(once()).will(invoke(FailCommandOutputPoll));
329+ 
330+ const aclError executionResult = executor.ExecuteCompilationPlan(plan);
331+ 
332+ GlobalMockObject::verify();
333+ EXPECT_EQ(executionResult, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
334+}
335+ 
336+TEST(CompilationPlanExecutorTest, ReportsCommandLeaderInspectionFailure)
337+{
338+ KernelSpecializationDiagnostics diagnostics(fs::path(), "executor_waitid_failure");
339+ CompilationCommandExecutionLimits limits{1s, 1s, 20ms, 1024U};
340+ CompilationPlanExecutor executor(diagnostics, limits);
341+ KernelCompilationPlan plan;
342+ plan.compilationCommands.push_back(CreateCommand(CompilationCommandKind::Compile, "/bin/sleep", {"1"}));
343+ MOCKER(waitid, int (*)(idtype_t, id_t, siginfo_t*, int)).expects(once()).will(invoke(FailCommandLeaderInspection));
344+ 
345+ const aclError executionResult = executor.ExecuteCompilationPlan(plan);
346+ 
347+ GlobalMockObject::verify();
348+ EXPECT_EQ(executionResult, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
349+}
350+} // namespace
@@ -0,0 +1,820 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "specialization/kernel_compilation_plan_builder.h"
12+ 
13+#include <gtest/gtest.h>
14+#include <nlohmann/json.hpp>
15+ 
16+#include <algorithm>
17+#include <boost/filesystem.hpp>
18+#include <boost/system/error_code.hpp>
19+#include <cstdlib>
20+#include <fstream>
21+#include <string>
22+#include <sys/stat.h>
23+#include <unistd.h>
24+ 
25+namespace {
26+namespace fs = boost::filesystem;
27+using Json = nlohmann::json;
28+using ascendc::aclrtc::CompilationCommandKind;
29+using ascendc::aclrtc::CompilationVariant;
30+using ascendc::aclrtc::KernelCompilationPlan;
31+using ascendc::aclrtc::KernelCompilationPlanBuilder;
32+using ascendc::aclrtc::NormalizedKernelSpecializationRequest;
33+ 
34+class ScopedEnvironmentVariable final {
35+public:
36+ ScopedEnvironmentVariable(const char* name, const std::string& value) : name_(name)
37+ {
38+ const char* previousValue = std::getenv(name);
39+ if (previousValue != nullptr) {
40+ previousValue_ = previousValue;
41+ wasSet_ = true;
42+ }
43+ setenv(name, value.c_str(), 1);
44+ }
45+ 
46+ ~ScopedEnvironmentVariable()
47+ {
48+ if (wasSet_) {
49+ setenv(name_.c_str(), previousValue_.c_str(), 1);
50+ } else {
51+ unsetenv(name_.c_str());
52+ }
53+ }
54+ 
55+private:
56+ std::string name_;
57+ std::string previousValue_;
58+ bool wasSet_{false};
59+};
60+ 
61+class KernelPlanTestWorkspace final {
62+public:
63+ KernelPlanTestWorkspace()
64+ {
65+ static uint64_t sequence = 0;
66+ rootPath_ = fs::temp_directory_path() /
67+ ("aclrtc_plan_test_" + std::to_string(getpid()) + "_" + std::to_string(sequence++));
68+ resourcePath_ = rootPath_ / "resource";
69+ toolchainPath_ = rootPath_ / "toolchain";
70+ sourceDirectoryPath_ = rootPath_ / "user_source";
71+ fs::create_directories(resourcePath_ / "resources/include");
72+ fs::create_directories(resourcePath_ / "resources/src");
73+ fs::create_directories(toolchainPath_ / "bin");
74+ fs::create_directories(sourceDirectoryPath_);
75+ WriteFile(sourceDirectoryPath_ / "add_custom.cpp", "// user source\n");
76+ WriteFile(resourcePath_ / "resources/include/constants.h", "@@STATIC_TILING@@\n");
77+ WriteFile(resourcePath_ / "resources/src/basic.cpp", "// basic\n");
78+ WriteFile(resourcePath_ / "resources/src/sk.cpp", "// sk\n");
79+ WriteExecutable(toolchainPath_ / "bin/bisheng");
80+ WriteExecutable(toolchainPath_ / "bin/llvm-objcopy");
81+ WriteExecutable(toolchainPath_ / "bin/ld.lld");
82+ }
83+ 
84+ ~KernelPlanTestWorkspace()
85+ {
86+ boost::system::error_code ignoredError;
87+ fs::remove_all(rootPath_, ignoredError);
88+ }
89+ 
90+ const fs::path& ResourcePath() const { return resourcePath_; }
91+ const fs::path& ToolchainPath() const { return toolchainPath_; }
92+ const fs::path& SourceDirectoryPath() const { return sourceDirectoryPath_; }
93+ 
94+private:
95+ static void WriteFile(const fs::path& path, const std::string& contents)
96+ {
97+ std::ofstream stream(path.string());
98+ stream << contents;
99+ }
100+ 
101+ static void WriteExecutable(const fs::path& path)
102+ {
103+ WriteFile(path, "#!/bin/sh\nexit 0\n");
104+ chmod(path.c_str(), 0755);
105+ }
106+ 
107+ fs::path rootPath_;
108+ fs::path resourcePath_;
109+ fs::path toolchainPath_;
110+ fs::path sourceDirectoryPath_;
111+};
112+ 
113+Json CreateManifest()
114+{
115+ return Json{
116+ {"schema_version", "1.0"},
117+ {"source_file", "add_custom.cpp"},
118+ {"options",
119+ {{"common_compile", Json::array({"-I", "${source_file_path}", "-I${env:ACLRTC_TEST_TOOLCHAIN}/include"})}}},
120+ {"kernels",
121+ Json::array(
122+ {{{"kernel_name", "other_kernel"}, {"unsupported_future_field", Json::object()}},
123+ {{"kernel_name", "add_custom_100000"},
124+ {"constant_infos", Json::array(
125+ {{{"parameter_index", 0},
126+ {"byte_size", 2},
127+ {"file", "${resource}/resources/include/constants.h"},
128+ {"template", "@@STATIC_TILING@@"}}})},
129+ {"link_options", Json::array({"-m", "aicorelinux", "-r", "-Ttext=0", "-q", "-x"})},
130+ {"objects",
131+ Json::array(
132+ {{{"object_name", "basic"},
133+ {"object_type", "basic"},
134+ {"commands",
135+ Json::array(
136+ {{{"type", "compile"},
137+ {"stage", 0},
138+ {"cmd", Json::array(
139+ {"${env:ACLRTC_TEST_TOOLCHAIN}/bin/bisheng", "${options:common_compile}",
140+ "${resource}/resources/src/basic.cpp", "-o", "${output}/basic.o"})}}})},
141+ {"outputs", Json::array({"${output}/basic.o"})}},
142+ {{"object_name", "sk"},
143+ {"object_type", "sk"},
144+ {"commands", Json::array(
145+ {{{"type", "compile"},
146+ {"stage", 0},
147+ {"cmd", Json::array(
148+ {"${env:ACLRTC_TEST_SK_TOOLCHAIN}/bin/bisheng",
149+ "${resource}/resources/src/sk.cpp", "-o", "${output}/sk.o"})}},
150+ {{"type", "objcopy"},
151+ {"stage", 1},
152+ {"cmd", Json::array(
153+ {"${env:ACLRTC_TEST_TOOLCHAIN}/bin/llvm-objcopy",
154+ "--redefine-sym=old_symbol=new_symbol", "${output}/sk.o",
155+ "${output}/sk_split.o"})}}})},
156+ {"outputs", Json::array({"${output}/sk.o", "${output}/sk_split.o"})}}})}}})}};
157+}
158+ 
159+NormalizedKernelSpecializationRequest CreateRequest(
160+ const KernelPlanTestWorkspace& workspace, const void* const* argumentAddresses, const uint64_t* argumentByteCounts)
161+{
162+ NormalizedKernelSpecializationRequest request;
163+ request.specializationSessionId = "session_1";
164+ request.resourceId = "resource";
165+ request.kernelName = "add_custom_100000";
166+ request.outputElfPath = workspace.ResourcePath() / "published.elf";
167+ request.kernelArgumentCount = 1;
168+ request.borrowedKernelArgumentDataPointers = argumentAddresses;
169+ request.borrowedKernelArgumentByteCounts = argumentByteCounts;
170+ return request;
171+}
172+ 
173+TEST(KernelCompilationPlanBuilderTest, BuildsBasicPlanFromOnlySelectedManifestFields)
174+{
175+ KernelPlanTestWorkspace workspace;
176+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
177+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
178+ unsetenv("ACLRTC_TEST_SK_TOOLCHAIN");
179+ const uint8_t tilingBytes[] = {0x01, 0xfe};
180+ const void* argumentAddresses[] = {tilingBytes};
181+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
182+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);
183+ request.compilerOptions.basicOptions = {"-g", "-DVALUE=1"};
184+ 
185+ KernelCompilationPlan plan;
186+ EXPECT_EQ(
187+ KernelCompilationPlanBuilder(
188+ request, CreateManifest(), workspace.ResourcePath(), workspace.SourceDirectoryPath())
189+ .BuildCompilationPlan(plan),
190+ 0);
191+ 
192+ ASSERT_EQ(plan.sourcePatches.size(), 1U);
193+ EXPECT_EQ(plan.sourcePatches[0].targetFilePath, workspace.ResourcePath() / "resources/include/constants.h");
194+ EXPECT_EQ(plan.sourcePatches[0].replacementText, "{0x01, 0xfe}");
195+ 
196+ ASSERT_EQ(plan.compilationCommands.size(), 2U);
197+ const auto& compileCommand = plan.compilationCommands[0];
198+ EXPECT_EQ(compileCommand.commandKind, CompilationCommandKind::Compile);
199+ EXPECT_EQ(compileCommand.executablePath, workspace.ToolchainPath() / "bin/bisheng");
200+ EXPECT_EQ(
201+ compileCommand.commandArguments,
202+ (std::vector<std::string>{
203+ "-I", workspace.SourceDirectoryPath().string(), "-I" + (workspace.ToolchainPath() / "include").string(),
204+ (workspace.ResourcePath() / "resources/src/basic.cpp").string(), "-g", "-DVALUE=1", "-o",
205+ (workspace.ResourcePath() / ".aclrtc_session_1/outputs/basic.o").string()}));
206+ EXPECT_EQ(plan.compilationCommands[1].commandKind, CompilationCommandKind::Link);
207+ EXPECT_EQ(plan.compilationCommands[1].executablePath, workspace.ToolchainPath() / "bin/ld.lld");
208+ EXPECT_EQ(plan.linkedKernelElfPath, workspace.ResourcePath() / ".aclrtc_session_1/outputs/linked_kernel.elf");
209+}
210+ 
211+TEST(KernelCompilationPlanBuilderTest, BuildsPlanWithoutOptionalExternalSourceFile)
212+{
213+ KernelPlanTestWorkspace workspace;
214+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
215+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
216+ const uint8_t tilingBytes[] = {0x01, 0x02};
217+ const void* argumentAddresses[] = {tilingBytes};
218+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
219+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);
220+ Json manifest = CreateManifest();
221+ manifest.erase("source_file");
222+ manifest["options"]["common_compile"] = Json::array({"-I${env:ACLRTC_TEST_TOOLCHAIN}/include"});
223+ 
224+ KernelCompilationPlan plan;
225+ EXPECT_EQ(
226+ KernelCompilationPlanBuilder(request, manifest, workspace.ResourcePath(), fs::path())
227+ .BuildCompilationPlan(plan),
228+ ascendc::aclrtc::ACLRTC_SUCCESS);
229+}
230+ 
231+TEST(KernelCompilationPlanBuilderTest, RejectsCompileCommandWithoutOutputPath)
232+{
233+ KernelPlanTestWorkspace workspace;
234+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
235+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
236+ const uint8_t tilingBytes[] = {0x01, 0x02};
237+ const void* argumentAddresses[] = {tilingBytes};
238+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
239+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);
240+ Json manifest = CreateManifest();
241+ manifest["kernels"][1]["objects"][0]["commands"][0]["cmd"].erase(4);
242+ 
243+ KernelCompilationPlan plan;
244+ EXPECT_EQ(
245+ KernelCompilationPlanBuilder(request, manifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
246+ .BuildCompilationPlan(plan),
247+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
248+}
249+ 
250+TEST(KernelCompilationPlanBuilderTest, DoesNotTreatNonOutputOptionAsCompilerOutput)
251+{
252+ KernelPlanTestWorkspace workspace;
253+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
254+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
255+ const uint8_t tilingBytes[] = {0x01, 0x02};
256+ const void* argumentAddresses[] = {tilingBytes};
257+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
258+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);
259+ Json manifest = CreateManifest();
260+ manifest["kernels"][1]["objects"][0]["commands"][0]["cmd"][3] = "-opaque-pointers";
261+ 
262+ KernelCompilationPlan plan;
263+ EXPECT_EQ(
264+ KernelCompilationPlanBuilder(request, manifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
265+ .BuildCompilationPlan(plan),
266+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
267+}
268+ 
269+TEST(KernelCompilationPlanBuilderTest, AcceptsCommandWithoutStageAndPreservesManifestOrder)
270+{
271+ KernelPlanTestWorkspace workspace;
272+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
273+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
274+ const uint8_t tilingBytes[] = {0x01, 0x02};
275+ const void* argumentAddresses[] = {tilingBytes};
276+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
277+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);
278+ Json manifest = CreateManifest();
279+ manifest["kernels"][1]["objects"][0]["commands"][0].erase("stage");
280+ 
281+ KernelCompilationPlan plan;
282+ ASSERT_EQ(
283+ KernelCompilationPlanBuilder(request, manifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
284+ .BuildCompilationPlan(plan),
285+ 0);
286+ ASSERT_EQ(plan.compilationCommands.size(), 2U);
287+ EXPECT_EQ(plan.compilationCommands[0].diagnosticLabel, "basic/compile[0]");
288+ EXPECT_FALSE(plan.compilationCommands[0].parallelStage.has_value());
289+ EXPECT_EQ(plan.compilationCommands[1].commandKind, CompilationCommandKind::Link);
290+ EXPECT_FALSE(plan.compilationCommands[1].parallelStage.has_value());
291+}
292+ 
293+TEST(KernelCompilationPlanBuilderTest, PreservesExplicitStagesAndManifestCommandOrder)
294+{
295+ KernelPlanTestWorkspace workspace;
296+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
297+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
298+ ScopedEnvironmentVariable skToolchainEnvironment("ACLRTC_TEST_SK_TOOLCHAIN", workspace.ToolchainPath().string());
299+ const uint8_t tilingBytes[] = {0x01, 0x02};
300+ const void* argumentAddresses[] = {tilingBytes};
301+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
302+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);
303+ request.compilationVariant = CompilationVariant::BasicWithSuperKernel;
304+ request.compilerOptions.superKernelOptions = {"-g"};
305+ 
306+ KernelCompilationPlan plan;
307+ ASSERT_EQ(
308+ KernelCompilationPlanBuilder(
309+ request, CreateManifest(), workspace.ResourcePath(), workspace.SourceDirectoryPath())
310+ .BuildCompilationPlan(plan),
311+ 0);
312+ ASSERT_EQ(plan.compilationCommands.size(), 4U);
313+ EXPECT_EQ(plan.compilationCommands[0].diagnosticLabel, "basic/compile[0]");
314+ EXPECT_EQ(plan.compilationCommands[1].diagnosticLabel, "sk/compile[0]");
315+ EXPECT_EQ(plan.compilationCommands[2].diagnosticLabel, "sk/objcopy[1]");
316+ EXPECT_EQ(plan.compilationCommands[3].diagnosticLabel, "link");
317+ EXPECT_EQ(plan.compilationCommands[0].parallelStage, 0U);
318+ EXPECT_EQ(plan.compilationCommands[1].parallelStage, 0U);
319+ EXPECT_EQ(plan.compilationCommands[2].parallelStage, 1U);
320+ EXPECT_FALSE(plan.compilationCommands[3].parallelStage.has_value());
321+ EXPECT_NE(
322+ std::find(
323+ plan.compilationCommands[1].commandArguments.begin(), plan.compilationCommands[1].commandArguments.end(),
324+ "-g"),
325+ plan.compilationCommands[1].commandArguments.end());
326+}
327+ 
328+TEST(KernelCompilationPlanBuilderTest, SelectsAllRequestedObjectsInManifestOrder)
329+{
330+ KernelPlanTestWorkspace workspace;
331+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
332+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
333+ ScopedEnvironmentVariable skToolchainEnvironment("ACLRTC_TEST_SK_TOOLCHAIN", workspace.ToolchainPath().string());
334+ const uint8_t tilingBytes[] = {0x01, 0x02};
335+ const void* argumentAddresses[] = {tilingBytes};
336+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
337+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);
338+ request.compilationVariant = CompilationVariant::BasicWithSuperKernel;
339+ 
340+ Json manifest = CreateManifest();
341+ Json secondBasicObject = manifest["kernels"][1]["objects"][0];
342+ secondBasicObject["object_name"] = "basic_aux";
343+ secondBasicObject["commands"][0]["cmd"][4] = "${output}/basic_aux.o";
344+ secondBasicObject["outputs"][0] = "${output}/basic_aux.o";
345+ manifest["kernels"][1]["objects"].push_back(secondBasicObject);
346+ 
347+ Json secondSuperKernelObject = manifest["kernels"][1]["objects"][1];
348+ secondSuperKernelObject["object_name"] = "sk_aux";
349+ secondSuperKernelObject["commands"][0]["cmd"][3] = "${output}/sk_aux.o";
350+ secondSuperKernelObject["commands"][1]["cmd"][2] = "${output}/sk_aux.o";
351+ secondSuperKernelObject["commands"][1]["cmd"][3] = "${output}/sk_aux_split.o";
352+ secondSuperKernelObject["outputs"] = Json::array({"${output}/sk_aux.o", "${output}/sk_aux_split.o"});
353+ manifest["kernels"][1]["objects"].push_back(secondSuperKernelObject);
354+ 
355+ KernelCompilationPlan plan;
356+ ASSERT_EQ(
357+ KernelCompilationPlanBuilder(request, manifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
358+ .BuildCompilationPlan(plan),
359+ 0);
360+ 
361+ ASSERT_EQ(plan.compilationCommands.size(), 7U);
362+ EXPECT_EQ(plan.compilationCommands[0].diagnosticLabel, "basic/compile[0]");
363+ EXPECT_EQ(plan.compilationCommands[1].diagnosticLabel, "sk/compile[0]");
364+ EXPECT_EQ(plan.compilationCommands[2].diagnosticLabel, "sk/objcopy[1]");
365+ EXPECT_EQ(plan.compilationCommands[3].diagnosticLabel, "basic_aux/compile[0]");
366+ EXPECT_EQ(plan.compilationCommands[4].diagnosticLabel, "sk_aux/compile[0]");
367+ EXPECT_EQ(plan.compilationCommands[5].diagnosticLabel, "sk_aux/objcopy[1]");
368+ EXPECT_EQ(plan.compilationCommands[6].diagnosticLabel, "link");
369+ 
370+ const std::vector<std::string>& linkArguments = plan.compilationCommands.back().commandArguments;
371+ EXPECT_NE(
372+ std::find(
373+ linkArguments.begin(), linkArguments.end(),
374+ (workspace.ResourcePath() / ".aclrtc_session_1/outputs/basic_aux.o").string()),
375+ linkArguments.end());
376+ EXPECT_NE(
377+ std::find(
378+ linkArguments.begin(), linkArguments.end(),
379+ (workspace.ResourcePath() / ".aclrtc_session_1/outputs/sk_aux_split.o").string()),
380+ linkArguments.end());
381+}
382+ 
383+TEST(KernelCompilationPlanBuilderTest, RequiresOnlyEnvironmentUsedBySelectedObjects)
384+{
385+ KernelPlanTestWorkspace workspace;
386+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
387+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
388+ unsetenv("ACLRTC_TEST_SK_TOOLCHAIN");
389+ const uint8_t tilingBytes[] = {0x01, 0x02};
390+ const void* argumentAddresses[] = {tilingBytes};
391+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
392+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);
393+ 
394+ KernelCompilationPlan plan;
395+ EXPECT_EQ(
396+ KernelCompilationPlanBuilder(
397+ request, CreateManifest(), workspace.ResourcePath(), workspace.SourceDirectoryPath())
398+ .BuildCompilationPlan(plan),
399+ 0);
400+ 
401+ request.compilationVariant = CompilationVariant::BasicWithSuperKernel;
402+ EXPECT_EQ(
403+ KernelCompilationPlanBuilder(
404+ request, CreateManifest(), workspace.ResourcePath(), workspace.SourceDirectoryPath())
405+ .BuildCompilationPlan(plan),
406+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
407+}
408+ 
409+TEST(KernelCompilationPlanBuilderTest, RejectsMalformedEnvironmentPlaceholders)
410+{
411+ KernelPlanTestWorkspace workspace;
412+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
413+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
414+ const uint8_t tilingBytes[] = {0x01, 0x02};
415+ const void* argumentDataPointers[] = {tilingBytes};
416+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
417+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentDataPointers, argumentByteCounts);
418+ KernelCompilationPlan plan;
419+ 
420+ Json manifestWithUnterminatedPlaceholder = CreateManifest();
421+ manifestWithUnterminatedPlaceholder["kernels"][1]["objects"][0]["commands"][0]["cmd"][2] =
422+ "${env:ACLRTC_TEST_TOOLCHAIN";
423+ EXPECT_EQ(
424+ KernelCompilationPlanBuilder(
425+ request, manifestWithUnterminatedPlaceholder, workspace.ResourcePath(), workspace.SourceDirectoryPath())
426+ .BuildCompilationPlan(plan),
427+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
428+ 
429+ Json manifestWithEmptyEnvironmentName = CreateManifest();
430+ manifestWithEmptyEnvironmentName["kernels"][1]["objects"][0]["commands"][0]["cmd"][2] = "${env:}";
431+ EXPECT_EQ(
432+ KernelCompilationPlanBuilder(
433+ request, manifestWithEmptyEnvironmentName, workspace.ResourcePath(), workspace.SourceDirectoryPath())
434+ .BuildCompilationPlan(plan),
435+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
436+}
437+ 
438+TEST(KernelCompilationPlanBuilderTest, RejectsUndefinedOptionReference)
439+{
440+ KernelPlanTestWorkspace workspace;
441+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
442+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
443+ const uint8_t tilingBytes[] = {0x01, 0x02};
444+ const void* argumentDataPointers[] = {tilingBytes};
445+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
446+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentDataPointers, argumentByteCounts);
447+ Json manifest = CreateManifest();
448+ manifest["kernels"][1]["objects"][0]["commands"][0]["cmd"][1] = "${options:missing}";
449+ 
450+ KernelCompilationPlan plan;
451+ EXPECT_EQ(
452+ KernelCompilationPlanBuilder(request, manifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
453+ .BuildCompilationPlan(plan),
454+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
455+}
456+ 
457+TEST(KernelCompilationPlanBuilderTest, DoesNotRecursivelyExpandEnvironmentOrUserOptions)
458+{
459+ KernelPlanTestWorkspace workspace;
460+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
461+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
462+ ScopedEnvironmentVariable literalEnvironment("ACLRTC_TEST_LITERAL", "${options:not_reexpanded}");
463+ const uint8_t tilingBytes[] = {0x01, 0x02};
464+ const void* argumentAddresses[] = {tilingBytes};
465+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
466+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);
467+ request.compilerOptions.basicOptions = {"${env:USER_OPTION_STAYS_LITERAL}"};
468+ Json manifest = CreateManifest();
469+ manifest["options"]["common_compile"].push_back("${env:ACLRTC_TEST_LITERAL}");
470+ 
471+ KernelCompilationPlan plan;
472+ ASSERT_EQ(
473+ KernelCompilationPlanBuilder(request, manifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
474+ .BuildCompilationPlan(plan),
475+ 0);
476+ const std::vector<std::string>& commandArguments = plan.compilationCommands.front().commandArguments;
477+ EXPECT_NE(
478+ std::find(commandArguments.begin(), commandArguments.end(), "${options:not_reexpanded}"),
479+ commandArguments.end());
480+ EXPECT_NE(
481+ std::find(commandArguments.begin(), commandArguments.end(), "${env:USER_OPTION_STAYS_LITERAL}"),
482+ commandArguments.end());
483+}
484+ 
485+TEST(KernelCompilationPlanBuilderTest, AcceptsKernelWithoutLinkOptions)
486+{
487+ KernelPlanTestWorkspace workspace;
488+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
489+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
490+ Json manifest = CreateManifest();
491+ manifest["kernels"][1].erase("link_options");
492+ const uint8_t tilingBytes[] = {0x01, 0x02};
493+ const void* argumentAddresses[] = {tilingBytes};
494+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
495+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentAddresses, argumentByteCounts);
496+ 
497+ KernelCompilationPlan plan;
498+ EXPECT_EQ(
499+ KernelCompilationPlanBuilder(request, manifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
500+ .BuildCompilationPlan(plan),
501+ 0);
502+ EXPECT_EQ(plan.sourcePatches.size(), 1U);
503+ ASSERT_EQ(plan.compilationCommands.size(), 2U);
504+ EXPECT_EQ(plan.compilationCommands.back().commandKind, CompilationCommandKind::Link);
505+}
506+ 
507+TEST(KernelCompilationPlanBuilderTest, RejectsKernelWithoutStaticConstants)
508+{
509+ KernelPlanTestWorkspace workspace;
510+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
511+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
512+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, nullptr, nullptr);
513+ KernelCompilationPlan plan;
514+ 
515+ Json manifestWithoutConstants = CreateManifest();
516+ manifestWithoutConstants["kernels"][1].erase("constant_infos");
517+ EXPECT_EQ(
518+ KernelCompilationPlanBuilder(
519+ request, manifestWithoutConstants, workspace.ResourcePath(), workspace.SourceDirectoryPath())
520+ .BuildCompilationPlan(plan),
521+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
522+ 
523+ Json manifestWithEmptyConstants = CreateManifest();
524+ manifestWithEmptyConstants["kernels"][1]["constant_infos"] = Json::array();
525+ EXPECT_EQ(
526+ KernelCompilationPlanBuilder(
527+ request, manifestWithEmptyConstants, workspace.ResourcePath(), workspace.SourceDirectoryPath())
528+ .BuildCompilationPlan(plan),
529+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
530+}
531+ 
532+TEST(KernelCompilationPlanBuilderTest, RejectsMissingRuntimeConstantBuffer)
533+{
534+ KernelPlanTestWorkspace workspace;
535+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
536+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
537+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, nullptr, nullptr);
538+ 
539+ KernelCompilationPlan plan;
540+ EXPECT_EQ(
541+ KernelCompilationPlanBuilder(
542+ request, CreateManifest(), workspace.ResourcePath(), workspace.SourceDirectoryPath())
543+ .BuildCompilationPlan(plan),
544+ ascendc::aclrtc::ACLRTC_ERROR_INVALID_INPUT);
545+}
546+ 
547+TEST(KernelCompilationPlanBuilderTest, RejectsMismatchedRuntimeConstantByteCount)
548+{
549+ KernelPlanTestWorkspace workspace;
550+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
551+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
552+ const uint8_t oversizedTilingBytes[] = {0x01, 0x02, 0x03};
553+ const void* argumentDataPointers[] = {oversizedTilingBytes};
554+ const uint64_t argumentByteCounts[] = {sizeof(oversizedTilingBytes)};
555+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentDataPointers, argumentByteCounts);
556+ 
557+ KernelCompilationPlan plan;
558+ EXPECT_EQ(
559+ KernelCompilationPlanBuilder(
560+ request, CreateManifest(), workspace.ResourcePath(), workspace.SourceDirectoryPath())
561+ .BuildCompilationPlan(plan),
562+ ascendc::aclrtc::ACLRTC_ERROR_INVALID_INPUT);
563+}
564+ 
565+TEST(KernelCompilationPlanBuilderTest, RejectsMissingResourceWorktree)
566+{
567+ KernelPlanTestWorkspace workspace;
568+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
569+ const uint8_t constantBytes[] = {0x01, 0x02};
570+ const void* argumentDataPointers[] = {constantBytes};
571+ const uint64_t argumentByteCounts[] = {sizeof(constantBytes)};
572+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentDataPointers, argumentByteCounts);
573+ KernelCompilationPlan plan;
574+ 
575+ const fs::path missingWorktreePath = workspace.ResourcePath() / "missing";
576+ EXPECT_EQ(
577+ KernelCompilationPlanBuilder(request, CreateManifest(), missingWorktreePath, workspace.SourceDirectoryPath())
578+ .BuildCompilationPlan(plan),
579+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
580+}
581+ 
582+TEST(KernelCompilationPlanBuilderTest, RejectsInvalidRuntimeConstantLocations)
583+{
584+ KernelPlanTestWorkspace workspace;
585+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
586+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
587+ const uint8_t constantBytes[] = {0x01, 0x02};
588+ const void* validArgumentDataPointers[] = {constantBytes};
589+ const void* nullArgumentDataPointers[] = {nullptr};
590+ const uint64_t argumentByteCounts[] = {sizeof(constantBytes)};
591+ KernelCompilationPlan plan;
592+ 
593+ Json outOfRangeManifest = CreateManifest();
594+ outOfRangeManifest["kernels"][1]["constant_infos"][0]["parameter_index"] = 1U;
595+ NormalizedKernelSpecializationRequest request =
596+ CreateRequest(workspace, validArgumentDataPointers, argumentByteCounts);
597+ EXPECT_EQ(
598+ KernelCompilationPlanBuilder(
599+ request, outOfRangeManifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
600+ .BuildCompilationPlan(plan),
601+ ascendc::aclrtc::ACLRTC_ERROR_INVALID_INPUT);
602+ 
603+ request = CreateRequest(workspace, nullArgumentDataPointers, argumentByteCounts);
604+ EXPECT_EQ(
605+ KernelCompilationPlanBuilder(
606+ request, CreateManifest(), workspace.ResourcePath(), workspace.SourceDirectoryPath())
607+ .BuildCompilationPlan(plan),
608+ ascendc::aclrtc::ACLRTC_ERROR_INVALID_INPUT);
609+}
610+ 
611+TEST(KernelCompilationPlanBuilderTest, RejectsMalformedManifestCommands)
612+{
613+ KernelPlanTestWorkspace workspace;
614+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
615+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
616+ const uint8_t constantBytes[] = {0x01, 0x02};
617+ const void* argumentDataPointers[] = {constantBytes};
618+ const uint64_t argumentByteCounts[] = {sizeof(constantBytes)};
619+ const NormalizedKernelSpecializationRequest request =
620+ CreateRequest(workspace, argumentDataPointers, argumentByteCounts);
621+ KernelCompilationPlan plan;
622+ 
623+ Json unsupportedCommandManifest = CreateManifest();
624+ unsupportedCommandManifest["kernels"][1]["objects"][0]["commands"][0]["type"] = "future-command";
625+ EXPECT_EQ(
626+ KernelCompilationPlanBuilder(
627+ request, unsupportedCommandManifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
628+ .BuildCompilationPlan(plan),
629+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
630+ 
631+ Json emptyCommandManifest = CreateManifest();
632+ emptyCommandManifest["kernels"][1]["objects"][0]["commands"][0]["cmd"] = Json::array();
633+ EXPECT_EQ(
634+ KernelCompilationPlanBuilder(
635+ request, emptyCommandManifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
636+ .BuildCompilationPlan(plan),
637+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
638+ 
639+ Json expandedExecutableManifest = CreateManifest();
640+ expandedExecutableManifest["kernels"][1]["objects"][0]["commands"][0]["cmd"][0] = "${options:common_compile}";
641+ EXPECT_EQ(
642+ KernelCompilationPlanBuilder(
643+ request, expandedExecutableManifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
644+ .BuildCompilationPlan(plan),
645+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
646+ 
647+ Json relativeExecutableManifest = CreateManifest();
648+ relativeExecutableManifest["kernels"][1]["objects"][0]["commands"][0]["cmd"][0] = "bisheng";
649+ EXPECT_EQ(
650+ KernelCompilationPlanBuilder(
651+ request, relativeExecutableManifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
652+ .BuildCompilationPlan(plan),
653+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
654+}
655+ 
656+TEST(KernelCompilationPlanBuilderTest, RequiresBasicAndRequestedSuperKernelObjects)
657+{
658+ KernelPlanTestWorkspace workspace;
659+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
660+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
661+ ScopedEnvironmentVariable skToolchainEnvironment("ACLRTC_TEST_SK_TOOLCHAIN", workspace.ToolchainPath().string());
662+ const uint8_t constantBytes[] = {0x01, 0x02};
663+ const void* argumentDataPointers[] = {constantBytes};
664+ const uint64_t argumentByteCounts[] = {sizeof(constantBytes)};
665+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentDataPointers, argumentByteCounts);
666+ KernelCompilationPlan plan;
667+ 
668+ Json manifestWithoutBasic = CreateManifest();
669+ manifestWithoutBasic["kernels"][1]["objects"].erase(0);
670+ EXPECT_EQ(
671+ KernelCompilationPlanBuilder(
672+ request, manifestWithoutBasic, workspace.ResourcePath(), workspace.SourceDirectoryPath())
673+ .BuildCompilationPlan(plan),
674+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
675+ 
676+ request.compilationVariant = CompilationVariant::BasicWithSuperKernel;
677+ Json manifestWithoutSuperKernel = CreateManifest();
678+ manifestWithoutSuperKernel["kernels"][1]["objects"].erase(1);
679+ EXPECT_EQ(
680+ KernelCompilationPlanBuilder(
681+ request, manifestWithoutSuperKernel, workspace.ResourcePath(), workspace.SourceDirectoryPath())
682+ .BuildCompilationPlan(plan),
683+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
684+}
685+ 
686+TEST(KernelCompilationPlanBuilderTest, RejectsUnresolvableLinkInputs)
687+{
688+ KernelPlanTestWorkspace workspace;
689+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
690+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
691+ const uint8_t constantBytes[] = {0x01, 0x02};
692+ const void* argumentDataPointers[] = {constantBytes};
693+ const uint64_t argumentByteCounts[] = {sizeof(constantBytes)};
694+ const NormalizedKernelSpecializationRequest request =
695+ CreateRequest(workspace, argumentDataPointers, argumentByteCounts);
696+ KernelCompilationPlan plan;
697+ unsetenv("ACLRTC_TEST_MISSING_LINK_INPUT");
698+ 
699+ Json unresolvedLinkOptionManifest = CreateManifest();
700+ unresolvedLinkOptionManifest["kernels"][1]["link_options"] = Json::array({"${env:ACLRTC_TEST_MISSING_LINK_INPUT}"});
701+ EXPECT_EQ(
702+ KernelCompilationPlanBuilder(
703+ request, unresolvedLinkOptionManifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
704+ .BuildCompilationPlan(plan),
705+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
706+ 
707+ Json unresolvedObjectOutputManifest = CreateManifest();
708+ unresolvedObjectOutputManifest["kernels"][1]["objects"][0]["outputs"][0] = "${env:ACLRTC_TEST_MISSING_LINK_INPUT}";
709+ EXPECT_EQ(
710+ KernelCompilationPlanBuilder(
711+ request, unresolvedObjectOutputManifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
712+ .BuildCompilationPlan(plan),
713+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
714+ 
715+ ScopedEnvironmentVariable missingLinkerEnvironment(
716+ "ASCEND_HOME_PATH", (workspace.ResourcePath() / "missing-toolchain").string());
717+ EXPECT_EQ(
718+ KernelCompilationPlanBuilder(
719+ request, CreateManifest(), workspace.ResourcePath(), workspace.SourceDirectoryPath())
720+ .BuildCompilationPlan(plan),
721+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
722+}
723+ 
724+TEST(KernelCompilationPlanBuilderTest, RejectsInvalidManifestSelectionInputs)
725+{
726+ KernelPlanTestWorkspace workspace;
727+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
728+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
729+ const uint8_t constantBytes[] = {0x01, 0x02};
730+ const void* argumentDataPointers[] = {constantBytes};
731+ const uint64_t argumentByteCounts[] = {sizeof(constantBytes)};
732+ NormalizedKernelSpecializationRequest request = CreateRequest(workspace, argumentDataPointers, argumentByteCounts);
733+ KernelCompilationPlan plan;
734+ 
735+ EXPECT_EQ(
736+ KernelCompilationPlanBuilder(request, CreateManifest(), "relative-worktree", workspace.SourceDirectoryPath())
737+ .BuildCompilationPlan(plan),
738+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
739+ 
740+ Json unsupportedSchemaManifest = CreateManifest();
741+ unsupportedSchemaManifest["schema_version"] = "2.0";
742+ EXPECT_EQ(
743+ KernelCompilationPlanBuilder(
744+ request, unsupportedSchemaManifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
745+ .BuildCompilationPlan(plan),
746+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
747+ 
748+ request.kernelName = "missing_kernel";
749+ EXPECT_EQ(
750+ KernelCompilationPlanBuilder(
751+ request, CreateManifest(), workspace.ResourcePath(), workspace.SourceDirectoryPath())
752+ .BuildCompilationPlan(plan),
753+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
754+ 
755+ request.kernelName = "add_custom_100000";
756+ Json malformedSchemaManifest = CreateManifest();
757+ malformedSchemaManifest["schema_version"] = Json::array();
758+ EXPECT_EQ(
759+ KernelCompilationPlanBuilder(
760+ request, malformedSchemaManifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
761+ .BuildCompilationPlan(plan),
762+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
763+}
764+ 
765+TEST(KernelCompilationPlanBuilderTest, RejectsMissingEnvironmentVariablesAtEachExpansionLayer)
766+{
767+ KernelPlanTestWorkspace workspace;
768+ ScopedEnvironmentVariable toolchainEnvironment("ACLRTC_TEST_TOOLCHAIN", workspace.ToolchainPath().string());
769+ ScopedEnvironmentVariable missingEnvironment("ACLRTC_TEST_MISSING_ENV", "temporarily-set");
770+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", workspace.ToolchainPath().string());
771+ ASSERT_EQ(unsetenv("ACLRTC_TEST_MISSING_ENV"), 0);
772+ const uint8_t tilingBytes[] = {0x01, 0x02};
773+ const void* argumentAddresses[] = {tilingBytes};
774+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
775+ const NormalizedKernelSpecializationRequest request =
776+ CreateRequest(workspace, argumentAddresses, argumentByteCounts);
777+ 
778+ Json manifest = CreateManifest();
779+ manifest["options"]["common_compile"] = Json::array({"${env:ACLRTC_TEST_MISSING_ENV}/include"});
780+ KernelCompilationPlan plan;
781+ EXPECT_NE(
782+ KernelCompilationPlanBuilder(request, manifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
783+ .BuildCompilationPlan(plan),
784+ ascendc::aclrtc::ACLRTC_SUCCESS);
785+ 
786+ manifest = CreateManifest();
787+ manifest["kernels"][1]["constant_infos"][0]["file"] = "${env:ACLRTC_TEST_MISSING_ENV}/constants.h";
788+ EXPECT_NE(
789+ KernelCompilationPlanBuilder(request, manifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
790+ .BuildCompilationPlan(plan),
791+ ascendc::aclrtc::ACLRTC_SUCCESS);
792+ 
793+ ASSERT_EQ(unsetenv("ASCEND_HOME_PATH"), 0);
794+ manifest = CreateManifest();
795+ EXPECT_NE(
796+ KernelCompilationPlanBuilder(request, manifest, workspace.ResourcePath(), workspace.SourceDirectoryPath())
797+ .BuildCompilationPlan(plan),
798+ ascendc::aclrtc::ACLRTC_SUCCESS);
799+}
800+ 
801+TEST(KernelCompilationPlanBuilderTest, ReportsResourceWorktreeInspectionErrors)
802+{
803+ KernelPlanTestWorkspace workspace;
804+ const fs::path selfReferentialLink = workspace.ResourcePath().parent_path() / "worktree-link-loop";
805+ boost::system::error_code symlinkError;
806+ fs::create_symlink(selfReferentialLink.filename(), selfReferentialLink, symlinkError);
807+ ASSERT_FALSE(symlinkError);
808+ const uint8_t tilingBytes[] = {0x01, 0x02};
809+ const void* argumentAddresses[] = {tilingBytes};
810+ const uint64_t argumentByteCounts[] = {sizeof(tilingBytes)};
811+ const NormalizedKernelSpecializationRequest request =
812+ CreateRequest(workspace, argumentAddresses, argumentByteCounts);
813+ 
814+ KernelCompilationPlan plan;
815+ EXPECT_NE(
816+ KernelCompilationPlanBuilder(request, CreateManifest(), selfReferentialLink, workspace.SourceDirectoryPath())
817+ .BuildCompilationPlan(plan),
818+ ascendc::aclrtc::ACLRTC_SUCCESS);
819+}
820+} // namespace
@@ -0,0 +1,286 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "specialization/kernel_compilation_workspace.h"
12+ 
13+#include <gtest/gtest.h>
14+ 
15+#include <boost/filesystem.hpp>
16+#include <boost/system/error_code.hpp>
17+#include <csignal>
18+#include <fstream>
19+#include <string>
20+#include <sys/resource.h>
21+#include <unistd.h>
22+ 
23+namespace {
24+namespace fs = boost::filesystem;
25+using ascendc::aclrtc::KernelCompilationPlan;
26+using ascendc::aclrtc::KernelCompilationWorkspace;
27+using ascendc::aclrtc::WorktreeRetentionPolicy;
28+ 
29+fs::path CreateWorkspacePath(const std::string& name)
30+{
31+ static uint64_t sequence = 0;
32+ const fs::path path =
33+ fs::temp_directory_path() / (name + "_" + std::to_string(getpid()) + "_" + std::to_string(sequence++));
34+ fs::create_directories(path);
35+ return path;
36+}
37+ 
38+void WriteFile(const fs::path& path, const std::string& text)
39+{
40+ fs::create_directories(path.parent_path());
41+ std::ofstream stream(path.string());
42+ stream << text;
43+}
44+ 
45+std::string ReadFile(const fs::path& path)
46+{
47+ std::ifstream stream(path.string());
48+ return std::string(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
49+}
50+ 
51+TEST(KernelCompilationWorkspaceTest, CreatesOutputDirectoriesAndAppliesAllPatchesPerFile)
52+{
53+ const fs::path worktreePath = CreateWorkspacePath("aclrtc_workspace_patch");
54+ const fs::path sourcePath = worktreePath / "resources/constants.h";
55+ WriteFile(sourcePath, "A=@@A@@; B=@@B@@;\n");
56+ KernelCompilationPlan plan;
57+ plan.requiredOutputDirectoryPaths = {worktreePath / "outputs/nested"};
58+ plan.sourcePatches = {
59+ {sourcePath, "@@A@@", "{0x01}"},
60+ {sourcePath, "@@B@@", "{0x02}"},
61+ };
62+ 
63+ KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
64+ EXPECT_EQ(workspace.CreateOutputDirectoriesAndApplySourcePatches(plan), 0);
65+ EXPECT_TRUE(fs::is_directory(worktreePath / "outputs/nested"));
66+ EXPECT_EQ(ReadFile(sourcePath), "A={0x01}; B={0x02};\n");
67+ 
68+ boost::system::error_code ignoredError;
69+ fs::remove_all(worktreePath, ignoredError);
70+}
71+ 
72+TEST(KernelCompilationWorkspaceTest, ReplacesEveryOccurrenceOfTheSameSourceTemplate)
73+{
74+ const fs::path worktreePath = CreateWorkspacePath("aclrtc_workspace_repeated_template");
75+ const fs::path sourcePath = worktreePath / "resources/constants.h";
76+ WriteFile(sourcePath, "FIRST=@@VALUE@@; SECOND=@@VALUE@@;\n");
77+ KernelCompilationPlan plan;
78+ plan.sourcePatches = {{sourcePath, "@@VALUE@@", "{0x01}"}};
79+ 
80+ KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
81+ EXPECT_EQ(workspace.CreateOutputDirectoriesAndApplySourcePatches(plan), ascendc::aclrtc::ACLRTC_SUCCESS);
82+ EXPECT_EQ(ReadFile(sourcePath), "FIRST={0x01}; SECOND={0x01};\n");
83+ 
84+ boost::system::error_code ignoredError;
85+ fs::remove_all(worktreePath, ignoredError);
86+}
87+ 
88+TEST(KernelCompilationWorkspaceTest, FailsWhenPatchTemplateIsMissing)
89+{
90+ const fs::path worktreePath = CreateWorkspacePath("aclrtc_workspace_missing_template");
91+ const fs::path sourcePath = worktreePath / "resources/constants.h";
92+ WriteFile(sourcePath, "unchanged\n");
93+ KernelCompilationPlan plan;
94+ plan.sourcePatches = {{sourcePath, "@@MISSING@@", "{0x01}"}};
95+ 
96+ KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
97+ EXPECT_EQ(workspace.CreateOutputDirectoriesAndApplySourcePatches(plan), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
98+ EXPECT_EQ(ReadFile(sourcePath), "unchanged\n");
99+ 
100+ boost::system::error_code ignoredError;
101+ fs::remove_all(worktreePath, ignoredError);
102+}
103+ 
104+TEST(KernelCompilationWorkspaceTest, FailsWhenPatchFileIsMissing)
105+{
106+ const fs::path worktreePath = CreateWorkspacePath("aclrtc_workspace_missing_file");
107+ const fs::path missingSourcePath = worktreePath / "resources/missing_constants.h";
108+ KernelCompilationPlan plan;
109+ plan.sourcePatches = {{missingSourcePath, "@@MISSING@@", "{0x01}"}};
110+ 
111+ KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
112+ EXPECT_EQ(workspace.CreateOutputDirectoriesAndApplySourcePatches(plan), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
113+ 
114+ boost::system::error_code ignoredError;
115+ fs::remove_all(worktreePath, ignoredError);
116+}
117+ 
118+TEST(KernelCompilationWorkspaceTest, FailsWhenPatchSourceCannotBeRead)
119+{
120+ const fs::path worktreePath = CreateWorkspacePath("aclrtc_workspace_unreadable_source");
121+ const fs::path directoryUsedAsSource = worktreePath / "resources/constants.h";
122+ fs::create_directories(directoryUsedAsSource);
123+ KernelCompilationPlan plan;
124+ plan.sourcePatches = {{directoryUsedAsSource, "@@VALUE@@", "{0x01}"}};
125+ 
126+ KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
127+ EXPECT_EQ(workspace.CreateOutputDirectoriesAndApplySourcePatches(plan), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
128+ EXPECT_TRUE(fs::is_directory(directoryUsedAsSource));
129+ 
130+ boost::system::error_code ignoredError;
131+ fs::remove_all(worktreePath, ignoredError);
132+}
133+ 
134+TEST(KernelCompilationWorkspaceTest, ReportsSourceReadFailureAfterFileOpen)
135+{
136+ KernelCompilationPlan plan;
137+ plan.sourcePatches = {{"/proc/self/mem", "@@VALUE@@", "{0x01}"}};
138+ 
139+ KernelCompilationWorkspace workspace({}, WorktreeRetentionPolicy::RetainAfterCompilation);
140+ EXPECT_EQ(workspace.CreateOutputDirectoriesAndApplySourcePatches(plan), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
141+}
142+ 
143+TEST(KernelCompilationWorkspaceTest, PreservesOriginalSourceWhenFinalPatchFlushFails)
144+{
145+ const fs::path worktreePath = CreateWorkspacePath("aclrtc_workspace_patch_flush_failure");
146+ const fs::path sourcePath = worktreePath / "resources/constants.h";
147+ WriteFile(sourcePath, "VALUE=@@VALUE@@;\n");
148+ KernelCompilationPlan plan;
149+ plan.sourcePatches = {{sourcePath, "@@VALUE@@", "{0x01}"}};
150+ 
151+ struct rlimit originalFileSizeLimit {};
152+ ASSERT_EQ(getrlimit(RLIMIT_FSIZE, &originalFileSizeLimit), 0);
153+ struct sigaction ignoreFileSizeSignal {};
154+ struct sigaction originalFileSizeSignal {};
155+ ignoreFileSizeSignal.sa_handler = SIG_IGN;
156+ ASSERT_EQ(sigemptyset(&ignoreFileSizeSignal.sa_mask), 0);
157+ ASSERT_EQ(sigaction(SIGXFSZ, &ignoreFileSizeSignal, &originalFileSizeSignal), 0);
158+ struct rlimit zeroFileSizeLimit = originalFileSizeLimit;
159+ zeroFileSizeLimit.rlim_cur = 0;
160+ ASSERT_EQ(setrlimit(RLIMIT_FSIZE, &zeroFileSizeLimit), 0);
161+ 
162+ KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
163+ const aclError patchResult = workspace.CreateOutputDirectoriesAndApplySourcePatches(plan);
164+ 
165+ const int restoreLimitResult = setrlimit(RLIMIT_FSIZE, &originalFileSizeLimit);
166+ const int restoreSignalResult = sigaction(SIGXFSZ, &originalFileSizeSignal, nullptr);
167+ ASSERT_EQ(restoreLimitResult, 0);
168+ ASSERT_EQ(restoreSignalResult, 0);
169+ EXPECT_EQ(patchResult, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
170+ EXPECT_EQ(ReadFile(sourcePath), "VALUE=@@VALUE@@;\n");
171+ 
172+ boost::system::error_code ignoredError;
173+ fs::remove_all(worktreePath, ignoredError);
174+}
175+ 
176+TEST(KernelCompilationWorkspaceTest, PreservesOriginalSourceWhenLargePatchWriteFails)
177+{
178+ const fs::path worktreePath = CreateWorkspacePath("aclrtc_workspace_patch_write_failure");
179+ const fs::path sourcePath = worktreePath / "resources/constants.h";
180+ WriteFile(sourcePath, "VALUE=@@VALUE@@;\n");
181+ KernelCompilationPlan plan;
182+ plan.sourcePatches = {{sourcePath, "@@VALUE@@", std::string(64U * 1024U, 'x')}};
183+ 
184+ struct rlimit originalFileSizeLimit {};
185+ ASSERT_EQ(getrlimit(RLIMIT_FSIZE, &originalFileSizeLimit), 0);
186+ struct sigaction ignoreFileSizeSignal {};
187+ struct sigaction originalFileSizeSignal {};
188+ ignoreFileSizeSignal.sa_handler = SIG_IGN;
189+ ASSERT_EQ(sigemptyset(&ignoreFileSizeSignal.sa_mask), 0);
190+ ASSERT_EQ(sigaction(SIGXFSZ, &ignoreFileSizeSignal, &originalFileSizeSignal), 0);
191+ struct rlimit zeroFileSizeLimit = originalFileSizeLimit;
192+ zeroFileSizeLimit.rlim_cur = 0U;
193+ ASSERT_EQ(setrlimit(RLIMIT_FSIZE, &zeroFileSizeLimit), 0);
194+ 
195+ KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
196+ const aclError patchResult = workspace.CreateOutputDirectoriesAndApplySourcePatches(plan);
197+ 
198+ const int restoreLimitResult = setrlimit(RLIMIT_FSIZE, &originalFileSizeLimit);
199+ const int restoreSignalResult = sigaction(SIGXFSZ, &originalFileSizeSignal, nullptr);
200+ ASSERT_EQ(restoreLimitResult, 0);
201+ ASSERT_EQ(restoreSignalResult, 0);
202+ EXPECT_EQ(patchResult, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
203+ EXPECT_EQ(ReadFile(sourcePath), "VALUE=@@VALUE@@;\n");
204+ 
205+ boost::system::error_code ignoredError;
206+ fs::remove_all(worktreePath, ignoredError);
207+}
208+ 
209+TEST(KernelCompilationWorkspaceTest, DeletesWorktreeByDefaultAndRetainsItWhenRequested)
210+{
211+ const fs::path removedWorktreePath = CreateWorkspacePath("aclrtc_workspace_remove");
212+ {
213+ KernelCompilationWorkspace workspace(removedWorktreePath, WorktreeRetentionPolicy::RemoveAfterCompilation);
214+ }
215+ EXPECT_FALSE(fs::exists(removedWorktreePath));
216+ 
217+ const fs::path retainedWorktreePath = CreateWorkspacePath("aclrtc_workspace_retain");
218+ {
219+ KernelCompilationWorkspace workspace(retainedWorktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
220+ }
221+ EXPECT_TRUE(fs::is_directory(retainedWorktreePath));
222+ boost::system::error_code ignoredError;
223+ fs::remove_all(retainedWorktreePath, ignoredError);
224+}
225+ 
226+TEST(KernelCompilationWorkspaceTest, RemovesWorktreeBeforeOutputPublicationWhenNotRetained)
227+{
228+ const fs::path worktreePath = CreateWorkspacePath("aclrtc_workspace_prepublish_cleanup");
229+ KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RemoveAfterCompilation);
230+ EXPECT_EQ(workspace.RemoveWorktreeBeforePublishingElfIfNeeded(), 0);
231+ EXPECT_FALSE(fs::exists(worktreePath));
232+}
233+ 
234+TEST(KernelCompilationWorkspaceTest, FailsWhenOutputDirectoryConflictsWithExistingFile)
235+{
236+ const fs::path worktreePath = CreateWorkspacePath("aclrtc_workspace_output_conflict");
237+ const fs::path conflictingOutputPath = worktreePath / "outputs";
238+ WriteFile(conflictingOutputPath, "not a directory");
239+ KernelCompilationPlan plan;
240+ plan.requiredOutputDirectoryPaths = {conflictingOutputPath / "nested"};
241+ 
242+ KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
243+ EXPECT_EQ(workspace.CreateOutputDirectoriesAndApplySourcePatches(plan), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
244+ EXPECT_TRUE(fs::is_regular_file(conflictingOutputPath));
245+ 
246+ boost::system::error_code ignoredError;
247+ fs::remove_all(worktreePath, ignoredError);
248+}
249+ 
250+TEST(KernelCompilationWorkspaceTest, PreservesSourceWhenTemporaryPatchFilenameIsTooLong)
251+{
252+ const fs::path worktreePath = CreateWorkspacePath("aclrtc_workspace_long_patch_name");
253+ const fs::path sourcePath = worktreePath / std::string(245U, 'a');
254+ WriteFile(sourcePath, "VALUE=@@VALUE@@;\n");
255+ KernelCompilationPlan plan;
256+ plan.sourcePatches = {{sourcePath, "@@VALUE@@", "{0x01}"}};
257+ 
258+ KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
259+ EXPECT_EQ(workspace.CreateOutputDirectoriesAndApplySourcePatches(plan), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
260+ EXPECT_EQ(ReadFile(sourcePath), "VALUE=@@VALUE@@;\n");
261+ 
262+ boost::system::error_code ignoredError;
263+ fs::remove_all(worktreePath, ignoredError);
264+}
265+ 
266+TEST(KernelCompilationWorkspaceTest, PrepublicationCleanupNoOpsForEmptyAndRetainedWorktrees)
267+{
268+ KernelCompilationWorkspace emptyWorkspace({}, WorktreeRetentionPolicy::RemoveAfterCompilation);
269+ EXPECT_EQ(emptyWorkspace.RemoveWorktreeBeforePublishingElfIfNeeded(), ascendc::aclrtc::ACLRTC_SUCCESS);
270+ 
271+ const fs::path retainedWorktreePath = CreateWorkspacePath("aclrtc_workspace_retained_prepublish");
272+ KernelCompilationWorkspace retainedWorkspace(retainedWorktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
273+ EXPECT_EQ(retainedWorkspace.RemoveWorktreeBeforePublishingElfIfNeeded(), ascendc::aclrtc::ACLRTC_SUCCESS);
274+ EXPECT_TRUE(fs::is_directory(retainedWorktreePath));
275+ 
276+ boost::system::error_code ignoredError;
277+ fs::remove_all(retainedWorktreePath, ignoredError);
278+}
279+ 
280+TEST(KernelCompilationWorkspaceTest, ReportsWorktreeRemovalFailure)
281+{
282+ KernelCompilationWorkspace workspace("/proc/self/status", WorktreeRetentionPolicy::RemoveAfterCompilation);
283+ EXPECT_EQ(workspace.RemoveWorktreeBeforePublishingElfIfNeeded(), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
284+ EXPECT_EQ(workspace.GetWorktreePath(), "/proc/self/status");
285+}
286+} // namespace
@@ -0,0 +1,308 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "acl/acl_rt_compile.h"
12+#include "specialization/kernel_specialization_types.h"
13+ 
14+#include <boost/filesystem.hpp>
15+#include <gtest/gtest.h>
16+ 
17+#include <array>
18+#include <cstddef>
19+#include <cstdint>
20+#include <string>
21+#include <type_traits>
22+#include <vector>
23+ 
24+#define ACLRTC_STRINGIFY_IMPL(value) #value
25+#define ACLRTC_STRINGIFY(value) ACLRTC_STRINGIFY_IMPL(value)
26+ 
27+namespace {
28+constexpr aclError ACL_ERROR_RTC_INVALID_INPUT = 176001;
29+constexpr aclError ACL_ERROR_RTC_INVALID_OPTION = 176002;
30+constexpr char RESOURCE_ID[] = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
31+ 
32+aclrtcKernelSpecRequest CreateValidRequest()
33+{
34+ aclrtcKernelSpecRequest request{};
35+ request.resourceId = RESOURCE_ID;
36+ request.kernelEntry = "add_custom_100000";
37+ return request;
38+}
39+ 
40+TEST(KernelSpecializationApiTest, RequestLayoutIsStableAndExtensible)
41+{
42+ static_assert(std::is_standard_layout_v<aclrtcKernelSpecRequest>);
43+ static_assert(std::extent_v<decltype(aclrtcKernelSpecRequest::reserved)> == 256);
44+ static_assert(std::is_same_v<decltype(aclrtcKernelSpecRequest::argsCount), uint64_t>);
45+ static_assert(std::is_same_v<decltype(aclrtcKernelSpecRequest::optionCount), uint64_t>);
46+ static_assert(std::is_same_v<decltype(aclrtcKernelSpecRequest::skOptionCount), uint64_t>);
47+ static_assert(offsetof(aclrtcKernelSpecRequest, resourceId) == 0U);
48+ static_assert(offsetof(aclrtcKernelSpecRequest, kernelEntry) == 8U);
49+ static_assert(offsetof(aclrtcKernelSpecRequest, argsCount) == 16U);
50+ static_assert(offsetof(aclrtcKernelSpecRequest, options) == 40U);
51+ static_assert(offsetof(aclrtcKernelSpecRequest, skOptions) == 56U);
52+ static_assert(offsetof(aclrtcKernelSpecRequest, reserved) == 72U);
53+ static_assert(sizeof(aclrtcKernelSpecRequest) == 328U);
54+}
55+ 
56+TEST(KernelSpecializationApiTest, PublicApiVisibilityMacroIsEmptyForConsumers)
57+{
58+ EXPECT_STREQ(ACLRTC_STRINGIFY(ACLRTC_API), "");
59+}
60+ 
61+TEST(KernelSpecializationRequestTest, NormalizesBasicRequestAndCopiesOptions)
62+{
63+ const char* options[] = {"-g", "-DVALUE=1"};
64+ aclrtcKernelSpecRequest request = CreateValidRequest();
65+ request.options = options;
66+ request.optionCount = 2;
67+ 
68+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
69+ EXPECT_EQ(ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest), 0);
70+ EXPECT_EQ(normalizedRequest.resourceId, RESOURCE_ID);
71+ EXPECT_EQ(normalizedRequest.kernelName, "add_custom_100000");
72+ EXPECT_EQ(normalizedRequest.outputElfPath, "/tmp/kernel.elf");
73+ EXPECT_EQ(normalizedRequest.compilationVariant, ascendc::aclrtc::CompilationVariant::Basic);
74+ EXPECT_EQ(normalizedRequest.compilerOptions.basicOptions, (std::vector<std::string>{"-g", "-DVALUE=1"}));
75+ EXPECT_TRUE(normalizedRequest.compilerOptions.superKernelOptions.empty());
76+}
77+ 
78+TEST(KernelSpecializationRequestTest, AcceptsOpaqueResourceIdAndNormalizesRelativeOutputPath)
79+{
80+ aclrtcKernelSpecRequest request = CreateValidRequest();
81+ request.resourceId = "vendor/operator/resource-v1";
82+ 
83+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
84+ ASSERT_EQ(
85+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "relative/kernel.elf", normalizedRequest), 0);
86+ EXPECT_EQ(normalizedRequest.resourceId, request.resourceId);
87+ EXPECT_EQ(normalizedRequest.outputElfPath, boost::filesystem::absolute("relative/kernel.elf").lexically_normal());
88+}
89+ 
90+TEST(KernelSpecializationRequestTest, EnablesSuperKernelAndRemovesControlOption)
91+{
92+ const char* skOptions[] = {"-g", "--enable-super-kernel", "-DDCCI_OFF"};
93+ aclrtcKernelSpecRequest request = CreateValidRequest();
94+ request.skOptions = skOptions;
95+ request.skOptionCount = 3;
96+ 
97+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
98+ EXPECT_EQ(ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest), 0);
99+ EXPECT_EQ(normalizedRequest.compilationVariant, ascendc::aclrtc::CompilationVariant::BasicWithSuperKernel);
100+ EXPECT_EQ(normalizedRequest.compilerOptions.superKernelOptions, (std::vector<std::string>{"-g", "-DDCCI_OFF"}));
101+}
102+ 
103+TEST(KernelSpecializationRequestTest, IgnoresSuperKernelOptionsWithoutEnable)
104+{
105+ const char* skOptions[] = {"-g", "-oignored"};
106+ aclrtcKernelSpecRequest request = CreateValidRequest();
107+ request.skOptions = skOptions;
108+ request.skOptionCount = 2;
109+ 
110+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
111+ EXPECT_EQ(ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest), 0);
112+ EXPECT_EQ(normalizedRequest.compilationVariant, ascendc::aclrtc::CompilationVariant::Basic);
113+ EXPECT_TRUE(normalizedRequest.compilerOptions.superKernelOptions.empty());
114+}
115+ 
116+TEST(KernelSpecializationRequestTest, RejectsInvalidAbiFields)
117+{
118+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
119+ 
120+ aclrtcKernelSpecRequest request = CreateValidRequest();
121+ EXPECT_EQ(
122+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "", normalizedRequest),
123+ ACL_ERROR_RTC_INVALID_INPUT);
124+ 
125+ request = CreateValidRequest();
126+ request.resourceId = "";
127+ EXPECT_EQ(
128+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
129+ ACL_ERROR_RTC_INVALID_INPUT);
130+ 
131+ request = CreateValidRequest();
132+ request.reserved[1] = 1;
133+ EXPECT_EQ(
134+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
135+ ACL_ERROR_RTC_INVALID_INPUT);
136+ 
137+ request = CreateValidRequest();
138+ request.optionCount = 1;
139+ EXPECT_EQ(
140+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
141+ ACL_ERROR_RTC_INVALID_INPUT);
142+}
143+ 
144+TEST(KernelSpecializationRequestTest, RejectsSpecificRequestValidationFailures)
145+{
146+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
147+ 
148+ aclrtcKernelSpecRequest request = CreateValidRequest();
149+ request.reserved[7] = 1U;
150+ EXPECT_EQ(
151+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
152+ ACL_ERROR_RTC_INVALID_INPUT);
153+ 
154+ request = CreateValidRequest();
155+ request.optionCount = 1U;
156+ EXPECT_EQ(
157+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
158+ ACL_ERROR_RTC_INVALID_INPUT);
159+ 
160+ const char* options[] = {"-o"};
161+ request = CreateValidRequest();
162+ request.options = options;
163+ request.optionCount = 1U;
164+ EXPECT_EQ(
165+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
166+ ACL_ERROR_RTC_INVALID_OPTION);
167+}
168+ 
169+TEST(KernelSpecializationRequestTest, RejectsInvalidRequestInputs)
170+{
171+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
172+ 
173+ aclrtcKernelSpecRequest request = CreateValidRequest();
174+ request.resourceId = nullptr;
175+ EXPECT_EQ(
176+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
177+ ACL_ERROR_RTC_INVALID_INPUT);
178+ 
179+ request = CreateValidRequest();
180+ request.kernelEntry = "";
181+ EXPECT_EQ(
182+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
183+ ACL_ERROR_RTC_INVALID_INPUT);
184+ 
185+ const char* nullOption[] = {nullptr};
186+ request = CreateValidRequest();
187+ request.options = nullOption;
188+ request.optionCount = 1U;
189+ EXPECT_EQ(
190+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
191+ ACL_ERROR_RTC_INVALID_OPTION);
192+ 
193+ const char* optionWithControlByte[] = {"-DVALUE=line\nbreak"};
194+ request = CreateValidRequest();
195+ request.options = optionWithControlByte;
196+ request.optionCount = 1U;
197+ EXPECT_EQ(
198+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
199+ ACL_ERROR_RTC_INVALID_OPTION);
200+ 
201+ request = CreateValidRequest();
202+ request.skOptions = nullOption;
203+ request.skOptionCount = 1U;
204+ EXPECT_EQ(
205+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
206+ ACL_ERROR_RTC_INVALID_OPTION);
207+}
208+ 
209+TEST(KernelSpecializationRequestTest, RejectsCompilerOptionThatExceedsIndividualTextLimit)
210+{
211+ const std::string oversizedOption(1024U * 1024U + 1U, 'x');
212+ const char* options[] = {oversizedOption.c_str()};
213+ aclrtcKernelSpecRequest request = CreateValidRequest();
214+ request.options = options;
215+ request.optionCount = 1U;
216+ 
217+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
218+ EXPECT_EQ(
219+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
220+ ACL_ERROR_RTC_INVALID_OPTION);
221+}
222+ 
223+TEST(KernelSpecializationRequestTest, RejectsCompilerOptionsThatExceedCombinedTextLimit)
224+{
225+ const std::string maximumLengthOption(1024U * 1024U, 'x');
226+ const char* options[] = {
227+ maximumLengthOption.c_str(), maximumLengthOption.c_str(), maximumLengthOption.c_str(),
228+ maximumLengthOption.c_str(), maximumLengthOption.c_str()};
229+ aclrtcKernelSpecRequest request = CreateValidRequest();
230+ request.options = options;
231+ request.optionCount = 5U;
232+ 
233+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
234+ EXPECT_EQ(
235+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
236+ ACL_ERROR_RTC_INVALID_OPTION);
237+}
238+ 
239+TEST(KernelSpecializationRequestTest, RejectsTooManyCompilerOptions)
240+{
241+ const std::vector<const char*> options(4097U, "-g");
242+ aclrtcKernelSpecRequest request = CreateValidRequest();
243+ request.options = options.data();
244+ request.optionCount = options.size();
245+ 
246+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
247+ EXPECT_EQ(
248+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
249+ ACL_ERROR_RTC_INVALID_INPUT);
250+}
251+ 
252+TEST(KernelSpecializationRequestTest, RejectsOptionsThatCanRedirectCompilerInputsOrOutputs)
253+{
254+ const std::array<const char*, 6> invalidOptions = {"@response.rsp", "-o", "-onew.o", "--output",
255+ "--output=new.o", "line\nbreak"};
256+ 
257+ for (const char* invalidOption : invalidOptions) {
258+ SCOPED_TRACE(invalidOption);
259+ const char* options[] = {invalidOption};
260+ aclrtcKernelSpecRequest request = CreateValidRequest();
261+ request.options = options;
262+ request.optionCount = 1;
263+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
264+ EXPECT_EQ(
265+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
266+ ACL_ERROR_RTC_INVALID_OPTION);
267+ }
268+}
269+ 
270+TEST(KernelSpecializationRequestTest, RejectsSuperKernelOptionsThatRedirectCompilerOutputWhenForwarded)
271+{
272+ const char* skOptions[] = {"--enable-super-kernel", "-o", "unexpected.elf"};
273+ aclrtcKernelSpecRequest request = CreateValidRequest();
274+ request.skOptions = skOptions;
275+ request.skOptionCount = 3U;
276+ 
277+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
278+ EXPECT_EQ(
279+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
280+ ACL_ERROR_RTC_INVALID_OPTION);
281+}
282+ 
283+TEST(KernelSpecializationRequestTest, TreatsShellMetacharactersAsLiteralArgvText)
284+{
285+ const char* options[] = {"-DVALUE=$(touch /tmp/not-executed);still-literal"};
286+ aclrtcKernelSpecRequest request = CreateValidRequest();
287+ request.options = options;
288+ request.optionCount = 1;
289+ 
290+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
291+ EXPECT_EQ(ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest), 0);
292+ ASSERT_EQ(normalizedRequest.compilerOptions.basicOptions.size(), 1U);
293+ EXPECT_EQ(normalizedRequest.compilerOptions.basicOptions[0], options[0]);
294+}
295+ 
296+TEST(KernelSpecializationRequestTest, RejectsDuplicateSuperKernelEnableOption)
297+{
298+ const char* skOptions[] = {"--enable-super-kernel", "--enable-super-kernel"};
299+ aclrtcKernelSpecRequest request = CreateValidRequest();
300+ request.skOptions = skOptions;
301+ request.skOptionCount = 2;
302+ 
303+ ascendc::aclrtc::NormalizedKernelSpecializationRequest normalizedRequest;
304+ EXPECT_EQ(
305+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, "/tmp/kernel.elf", normalizedRequest),
306+ ACL_ERROR_RTC_INVALID_OPTION);
307+}
308+} // namespace
@@ -0,0 +1,301 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "specialization/kernel_specialization_diagnostics.h"
12+ 
13+#include <gtest/gtest.h>
14+#include <mockcpp/mockcpp.hpp>
15+#include <nlohmann/json.hpp>
16+ 
17+#include <boost/filesystem.hpp>
18+#include <boost/system/error_code.hpp>
19+#include <cerrno>
20+#include <csignal>
21+#include <cstdint>
22+#include <fcntl.h>
23+#include <fstream>
24+#include <string>
25+#include <sys/resource.h>
26+#include <sys/stat.h>
27+#include <sys/wait.h>
28+#include <unistd.h>
29+ 
30+namespace {
31+namespace fs = boost::filesystem;
32+using ascendc::aclrtc::CompilationCommand;
33+using ascendc::aclrtc::CompilationCommandKind;
34+using ascendc::aclrtc::CompilationProcessResult;
35+using ascendc::aclrtc::KernelSpecializationDiagnostics;
36+using ascendc::aclrtc::OutputPublicationStatus;
37+using namespace mockcpp;
38+ 
39+int RejectReplayScriptPermissionChange(const char*, mode_t)
40+{
41+ errno = EPERM;
42+ return -1;
43+}
44+ 
45+class ScopedCurrentDirectory final {
46+public:
47+ explicit ScopedCurrentDirectory(const fs::path& currentDirectory) : previousDirectory_(fs::current_path())
48+ {
49+ fs::current_path(currentDirectory);
50+ }
51+ 
52+ ~ScopedCurrentDirectory()
53+ {
54+ boost::system::error_code ignoredError;
55+ fs::current_path(previousDirectory_, ignoredError);
56+ }
57+ 
58+private:
59+ fs::path previousDirectory_;
60+};
61+ 
62+std::string ReadTextFile(const fs::path& filePath)
63+{
64+ std::ifstream inputStream(filePath.string());
65+ return std::string(std::istreambuf_iterator<char>(inputStream), std::istreambuf_iterator<char>());
66+}
67+ 
68+int ExecuteReplayScriptAndWait(const fs::path& replayScriptPath)
69+{
70+ const pid_t childProcessId = fork();
71+ if (childProcessId == 0) {
72+ execl(replayScriptPath.c_str(), replayScriptPath.c_str(), static_cast<char*>(nullptr));
73+ _exit(127);
74+ }
75+ if (childProcessId < 0) {
76+ return -1;
77+ }
78+ 
79+ int childStatus = 0;
80+ pid_t waitResult;
81+ do {
82+ waitResult = waitpid(childProcessId, &childStatus, 0);
83+ } while (waitResult < 0 && errno == EINTR);
84+ if (waitResult != childProcessId || !WIFEXITED(childStatus)) {
85+ return -1;
86+ }
87+ return WEXITSTATUS(childStatus);
88+}
89+ 
90+TEST(KernelSpecializationDiagnosticsTest, SavesOnlyReviewedDiagnosticFiles)
91+{
92+ static uint64_t sequence = 0U;
93+ const fs::path testRoot = fs::temp_directory_path() / ("aclrtc_diagnostics_test_" + std::to_string(getpid()) + "_" +
94+ std::to_string(sequence++));
95+ fs::create_directories(testRoot);
96+ {
97+ ScopedCurrentDirectory currentDirectory(testRoot);
98+ KernelSpecializationDiagnostics diagnostics(testRoot, "session_1");
99+ diagnostics.WriteManifestSnapshot(nlohmann::json{{"schema_version", "1.0"}});
100+ 
101+ const CompilationCommand command{
102+ CompilationCommandKind::Compile,
103+ 0U,
104+ "basic/compile[0]",
105+ "/bin/echo",
106+ {"argument with space", "quote'value"}};
107+ diagnostics.AppendCommandToReplayScript(command);
108+ const CompilationCommand workingDirectoryCheck{
109+ CompilationCommandKind::Compile,
110+ std::nullopt,
111+ "basic/compile[1]",
112+ "/bin/sh",
113+ {"-c", "test \"$PWD\" = \"$1\"", "replay-working-directory-check", testRoot.string()}};
114+ diagnostics.AppendCommandToReplayScript(workingDirectoryCheck);
115+ CompilationProcessResult processResult;
116+ processResult.capturedOutput = "compiler output\n";
117+ diagnostics.AppendCommandResultToCompilationLog(command, processResult);
118+ diagnostics.WriteSpecializationResult("/tmp/kernel.elf", OutputPublicationStatus::Published);
119+ }
120+ 
121+ const fs::path replayScriptPath = testRoot / "aclrtc_replay.sh";
122+ EXPECT_TRUE(fs::is_regular_file(testRoot / "aclrtc_manifest.json"));
123+ EXPECT_TRUE(fs::is_regular_file(replayScriptPath));
124+ EXPECT_TRUE(fs::is_regular_file(testRoot / "aclrtc_compile.log"));
125+ EXPECT_TRUE(fs::is_regular_file(testRoot / "aclrtc_result.json"));
126+ EXPECT_EQ(std::distance(fs::directory_iterator(testRoot), fs::directory_iterator()), 4);
127+ 
128+ const std::string replayScript = ReadTextFile(replayScriptPath);
129+ EXPECT_NE(replayScript.find("'/bin/echo' 'argument with space' 'quote'\"'\"'value'"), std::string::npos);
130+ 
131+ struct stat replayScriptStatus {};
132+ ASSERT_EQ(stat(replayScriptPath.c_str(), &replayScriptStatus), 0);
133+ EXPECT_EQ(replayScriptStatus.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO), S_IRWXU);
134+ EXPECT_EQ(ExecuteReplayScriptAndWait(replayScriptPath), 0);
135+ 
136+ const nlohmann::json result = nlohmann::json::parse(ReadTextFile(testRoot / "aclrtc_result.json"));
137+ EXPECT_EQ(result.at("specialization_session_id"), "session_1");
138+ EXPECT_EQ(result.at("output_path"), "/tmp/kernel.elf");
139+ EXPECT_TRUE(result.at("output_published"));
140+ EXPECT_FALSE(result.contains("result"));
141+ 
142+ boost::system::error_code ignoredError;
143+ fs::remove_all(testRoot, ignoredError);
144+}
145+ 
146+TEST(KernelSpecializationDiagnosticsTest, DisablesDiagnosticsWhenReplayScriptCannotBeCreated)
147+{
148+ const fs::path missingWorktreePath =
149+ fs::temp_directory_path() / fs::unique_path("aclrtc_missing_diagnostics_%%%%-%%%%") / "worktree";
150+ KernelSpecializationDiagnostics diagnostics(missingWorktreePath, "disabled_session");
151+ diagnostics.WriteManifestSnapshot(nlohmann::json{{"schema_version", "1.0"}});
152+ 
153+ const CompilationCommand command{CompilationCommandKind::Compile, std::nullopt, "compile", "/bin/true", {}};
154+ diagnostics.AppendCommandToReplayScript(command);
155+ diagnostics.AppendCommandResultToCompilationLog(command, CompilationProcessResult{});
156+ diagnostics.LogCommandFailureRecoveryHint();
157+ diagnostics.WriteSpecializationResult("/tmp/kernel.elf", OutputPublicationStatus::NotPublished);
158+ 
159+ EXPECT_FALSE(fs::exists(missingWorktreePath));
160+}
161+ 
162+TEST(KernelSpecializationDiagnosticsTest, RemovesReplayScriptWhenInitialWriteCannotBeFinalized)
163+{
164+ const fs::path testRoot = fs::temp_directory_path() / fs::unique_path("aclrtc_diagnostics_write_failure_%%%%-%%%%");
165+ fs::create_directories(testRoot);
166+ struct rlimit originalFileSizeLimit {};
167+ ASSERT_EQ(getrlimit(RLIMIT_FSIZE, &originalFileSizeLimit), 0);
168+ struct sigaction ignoreFileSizeSignal {};
169+ struct sigaction originalFileSizeSignal {};
170+ ignoreFileSizeSignal.sa_handler = SIG_IGN;
171+ ASSERT_EQ(sigemptyset(&ignoreFileSizeSignal.sa_mask), 0);
172+ ASSERT_EQ(sigaction(SIGXFSZ, &ignoreFileSizeSignal, &originalFileSizeSignal), 0);
173+ struct rlimit zeroFileSizeLimit = originalFileSizeLimit;
174+ zeroFileSizeLimit.rlim_cur = 0U;
175+ ASSERT_EQ(setrlimit(RLIMIT_FSIZE, &zeroFileSizeLimit), 0);
176+ 
177+ KernelSpecializationDiagnostics diagnostics(testRoot, "write_failure_session");
178+ 
179+ const int restoreLimitResult = setrlimit(RLIMIT_FSIZE, &originalFileSizeLimit);
180+ const int restoreSignalResult = sigaction(SIGXFSZ, &originalFileSizeSignal, nullptr);
181+ ASSERT_EQ(restoreLimitResult, 0);
182+ ASSERT_EQ(restoreSignalResult, 0);
183+ EXPECT_FALSE(fs::exists(testRoot / "aclrtc_replay.sh"));
184+ 
185+ boost::system::error_code ignoredError;
186+ fs::remove_all(testRoot, ignoredError);
187+}
188+ 
189+TEST(KernelSpecializationDiagnosticsTest, KeepsNonEmptyReplayPathWhenInitialOpenFails)
190+{
191+ const fs::path testRoot =
192+ fs::temp_directory_path() / fs::unique_path("aclrtc_diagnostics_replay_directory_%%%%-%%%%");
193+ const fs::path replayScriptDirectory = testRoot / "aclrtc_replay.sh";
194+ fs::create_directories(replayScriptDirectory);
195+ std::ofstream(replayScriptDirectory / "existing.log") << "keep this diagnostic evidence\n";
196+ 
197+ KernelSpecializationDiagnostics diagnostics(testRoot, "replay_directory_session");
198+ EXPECT_TRUE(fs::is_directory(replayScriptDirectory));
199+ EXPECT_TRUE(fs::is_regular_file(replayScriptDirectory / "existing.log"));
200+ 
201+ boost::system::error_code ignoredError;
202+ fs::remove_all(testRoot, ignoredError);
203+}
204+ 
205+TEST(KernelSpecializationDiagnosticsTest, DisablesDiagnosticsWhenReplayScriptCannotBeMadeExecutable)
206+{
207+ const fs::path testRoot = fs::temp_directory_path() / fs::unique_path("aclrtc_diagnostics_chmod_failure_%%%%-%%%%");
208+ fs::create_directories(testRoot);
209+ MOCKER(chmod, int (*)(const char*, mode_t)).expects(once()).will(invoke(RejectReplayScriptPermissionChange));
210+ 
211+ KernelSpecializationDiagnostics diagnostics(testRoot, "chmod_failure_session");
212+ 
213+ GlobalMockObject::verify();
214+ EXPECT_FALSE(fs::exists(testRoot / "aclrtc_replay.sh"));
215+ boost::system::error_code ignoredError;
216+ fs::remove_all(testRoot, ignoredError);
217+}
218+ 
219+TEST(KernelSpecializationDiagnosticsTest, HandlesMissingCurrentDirectoryDuringInitialization)
220+{
221+ const int originalDirectoryFileDescriptor = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
222+ ASSERT_GE(originalDirectoryFileDescriptor, 0);
223+ const fs::path removedCurrentDirectory =
224+ fs::temp_directory_path() / fs::unique_path("aclrtc_diagnostics_removed_cwd_%%%%-%%%%");
225+ fs::create_directories(removedCurrentDirectory);
226+ ASSERT_EQ(chdir(removedCurrentDirectory.c_str()), 0);
227+ ASSERT_EQ(rmdir(removedCurrentDirectory.c_str()), 0);
228+ 
229+ KernelSpecializationDiagnostics diagnostics(removedCurrentDirectory, "removed_cwd_session");
230+ 
231+ const int restoreDirectoryResult = fchdir(originalDirectoryFileDescriptor);
232+ const int closeDirectoryResult = close(originalDirectoryFileDescriptor);
233+ ASSERT_EQ(restoreDirectoryResult, 0);
234+ ASSERT_EQ(closeDirectoryResult, 0);
235+ EXPECT_FALSE(fs::exists(removedCurrentDirectory));
236+}
237+ 
238+TEST(KernelSpecializationDiagnosticsTest, IgnoresManifestSerializationFailure)
239+{
240+ const fs::path testRoot =
241+ fs::temp_directory_path() / fs::unique_path("aclrtc_diagnostics_invalid_manifest_%%%%-%%%%");
242+ fs::create_directories(testRoot);
243+ KernelSpecializationDiagnostics diagnostics(testRoot, "invalid_manifest_session");
244+ const nlohmann::json invalidUtf8Manifest = {{"invalid_utf8", std::string(1U, static_cast<char>(0xff))}};
245+ 
246+ diagnostics.WriteManifestSnapshot(invalidUtf8Manifest);
247+ 
248+ EXPECT_FALSE(fs::exists(testRoot / "aclrtc_manifest.json"));
249+ boost::system::error_code ignoredError;
250+ fs::remove_all(testRoot, ignoredError);
251+}
252+ 
253+TEST(KernelSpecializationDiagnosticsTest, RecordsTerminationKindsAndUnpublishedResult)
254+{
255+ const fs::path testRoot = fs::temp_directory_path() / fs::unique_path("aclrtc_diagnostics_states_%%%%-%%%%");
256+ fs::create_directories(testRoot);
257+ KernelSpecializationDiagnostics diagnostics(testRoot, "session_states");
258+ const CompilationCommand command{CompilationCommandKind::Compile, std::nullopt, "compile", "/bin/true", {}};
259+ 
260+ CompilationProcessResult processResult;
261+ processResult.termination = ascendc::aclrtc::CompilationProcessTermination::Signaled;
262+ processResult.terminationCode = SIGTERM;
263+ diagnostics.AppendCommandResultToCompilationLog(command, processResult);
264+ processResult.termination = ascendc::aclrtc::CompilationProcessTermination::TimedOut;
265+ diagnostics.AppendCommandResultToCompilationLog(command, processResult);
266+ processResult.termination = static_cast<ascendc::aclrtc::CompilationProcessTermination>(UINT32_MAX);
267+ diagnostics.AppendCommandResultToCompilationLog(command, processResult);
268+ diagnostics.LogCommandFailureRecoveryHint();
269+ diagnostics.WriteSpecializationResult("/tmp/not_published.elf", OutputPublicationStatus::NotPublished);
270+ 
271+ const std::string compilationLog = ReadTextFile(testRoot / "aclrtc_compile.log");
272+ EXPECT_NE(compilationLog.find("termination=signaled"), std::string::npos);
273+ EXPECT_NE(compilationLog.find("termination=timed_out"), std::string::npos);
274+ EXPECT_NE(compilationLog.find("termination=unknown"), std::string::npos);
275+ const nlohmann::json result = nlohmann::json::parse(ReadTextFile(testRoot / "aclrtc_result.json"));
276+ EXPECT_FALSE(result.at("output_published"));
277+ 
278+ boost::system::error_code ignoredError;
279+ fs::remove_all(testRoot, ignoredError);
280+}
281+ 
282+TEST(KernelSpecializationDiagnosticsTest, IgnoresDiagnosticWriteFailuresAfterWorktreeRemoval)
283+{
284+ const fs::path testRoot = fs::temp_directory_path() / fs::unique_path("aclrtc_diagnostics_removed_%%%%-%%%%");
285+ fs::create_directories(testRoot);
286+ KernelSpecializationDiagnostics diagnostics(testRoot, "removed_session");
287+ boost::system::error_code removeError;
288+ fs::remove_all(testRoot, removeError);
289+ ASSERT_FALSE(removeError);
290+ 
291+ const CompilationCommand command{
292+ CompilationCommandKind::Compile, std::nullopt, "compile", "/bin/true", {"argument"}};
293+ diagnostics.WriteManifestSnapshot(nlohmann::json{{"schema_version", "1.0"}});
294+ diagnostics.AppendCommandToReplayScript(command);
295+ diagnostics.AppendCommandResultToCompilationLog(command, CompilationProcessResult{});
296+ diagnostics.WriteSpecializationResult("/tmp/kernel.elf", OutputPublicationStatus::NotPublished);
297+ 
298+ EXPECT_FALSE(fs::exists(testRoot));
299+}
300+ 
301+} // namespace
@@ -0,0 +1,589 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "specialization/kernel_specialization.h"
12+#include "specialization/kernel_compilation_workspace.h"
13+#include "specialization/resource_registry.h"
14+ 
15+#include <gtest/gtest.h>
16+#include <nlohmann/json.hpp>
17+ 
18+#include <boost/filesystem.hpp>
19+#include <boost/system/error_code.hpp>
20+#include <csignal>
21+#include <cstdlib>
22+#include <fstream>
23+#include <memory>
24+#include <string>
25+#include <sys/resource.h>
26+#include <sys/stat.h>
27+#include <unistd.h>
28+#include <utility>
29+#include <vector>
30+ 
31+namespace {
32+namespace fs = boost::filesystem;
33+using Json = nlohmann::json;
34+using ascendc::aclrtc::KernelCompilationWorkspace;
35+using ascendc::aclrtc::KernelSpecializationSession;
36+using ascendc::aclrtc::MaterializedKernelCompilationResource;
37+using ascendc::aclrtc::NormalizedKernelSpecializationRequest;
38+using ascendc::aclrtc::WorktreeRetentionPolicy;
39+using ascendc::specialization_compile::ResourceEntry;
40+using ascendc::specialization_compile::ResourceFileData;
41+using ascendc::specialization_compile::ResourceRegistry;
42+using ascendc::specialization_compile::ResourceSourceType;
43+using ascendc::specialization_compile::ResourceStatus;
44+ 
45+enum class FakeCompilerOutcome : uint32_t {
46+ Success,
47+ Failure,
48+};
49+ 
50+constexpr char RESOURCE_ID[] = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
51+ 
52+class ScopedEnvironmentVariable final {
53+public:
54+ ScopedEnvironmentVariable(const char* name, const char* value) : name_(name)
55+ {
56+ const char* previousValue = std::getenv(name);
57+ if (previousValue != nullptr) {
58+ previousValue_ = previousValue;
59+ wasSet_ = true;
60+ }
61+ if (value == nullptr) {
62+ unsetenv(name);
63+ } else {
64+ setenv(name, value, 1);
65+ }
66+ }
67+ 
68+ ~ScopedEnvironmentVariable()
69+ {
70+ if (wasSet_) {
71+ setenv(name_.c_str(), previousValue_.c_str(), 1);
72+ } else {
73+ unsetenv(name_.c_str());
74+ }
75+ }
76+ 
77+private:
78+ std::string name_;
79+ std::string previousValue_;
80+ bool wasSet_{false};
81+};
82+ 
83+class EndToEndTestResource final {
84+public:
85+ explicit EndToEndTestResource(FakeCompilerOutcome compilerOutcome)
86+ {
87+ static uint64_t sequence = 0;
88+ rootPath_ = fs::temp_directory_path() /
89+ ("aclrtc_session_test_" + std::to_string(getpid()) + "_" + std::to_string(sequence++));
90+ worktreePath_ = rootPath_ / "worktree";
91+ sourceDirectoryPath_ = rootPath_ / "user";
92+ toolchainPath_ = rootPath_ / "toolchain";
93+ outputElfPath_ = rootPath_ / "output/kernel.elf";
94+ fs::create_directories(worktreePath_ / "resources/include");
95+ fs::create_directories(worktreePath_ / "resources/src");
96+ fs::create_directories(sourceDirectoryPath_);
97+ fs::create_directories(toolchainPath_ / "bin");
98+ fs::create_directories(outputElfPath_.parent_path());
99+ WriteFile(sourceDirectoryPath_ / "add_custom.cpp", "// user source\n");
100+ WriteFile(worktreePath_ / "resources/include/constants.h", "TILING=@@STATIC_TILING@@;\n");
101+ WriteFile(worktreePath_ / "resources/src/basic.cpp", "// basic source\n");
102+ WriteExecutable(
103+ toolchainPath_ / "bin/bisheng",
104+ compilerOutcome == FakeCompilerOutcome::Success ? CreateOutputScript("object") : "#!/bin/sh\nexit 2\n");
105+ WriteExecutable(toolchainPath_ / "bin/ld.lld", CreateOutputScript("linked-elf"));
106+ manifest_ = CreateManifest();
107+ }
108+ 
109+ ~EndToEndTestResource()
110+ {
111+ boost::system::error_code ignoredError;
112+ fs::remove_all(rootPath_, ignoredError);
113+ }
114+ 
115+ MaterializedKernelCompilationResource CreateMaterializedCompilationResource() const
116+ {
117+ return MaterializedKernelCompilationResource{manifest_, worktreePath_, sourceDirectoryPath_};
118+ }
119+ 
120+ const fs::path& ToolchainPath() const { return toolchainPath_; }
121+ const fs::path& WorktreePath() const { return worktreePath_; }
122+ const fs::path& SourceDirectoryPath() const { return sourceDirectoryPath_; }
123+ const fs::path& OutputElfPath() const { return outputElfPath_; }
124+ const Json& Manifest() const { return manifest_; }
125+ 
126+private:
127+ static void WriteFile(const fs::path& path, const std::string& contents)
128+ {
129+ std::ofstream stream(path.string());
130+ stream << contents;
131+ }
132+ 
133+ static void WriteExecutable(const fs::path& path, const std::string& contents)
134+ {
135+ WriteFile(path, contents);
136+ chmod(path.c_str(), 0755);
137+ }
138+ 
139+ static std::string CreateOutputScript(const char* contents)
140+ {
141+ return std::string("#!/bin/sh\n") +
142+ "while [ \"$#\" -gt 0 ]; do\n"
143+ " if [ \"$1\" = \"-o\" ]; then\n"
144+ " shift\n"
145+ " printf '" +
146+ contents +
147+ "' > \"$1\"\n"
148+ " exit 0\n"
149+ " fi\n"
150+ " shift\n"
151+ "done\n"
152+ "exit 3\n";
153+ }
154+ 
155+ static Json CreateManifest()
156+ {
157+ return Json{
158+ {"schema_version", "1.0"},
159+ {"source_file", "add_custom.cpp"},
160+ {"kernels", Json::array(
161+ {{{"kernel_name", "add_custom_100000"},
162+ {"constant_infos", Json::array(
163+ {{{"parameter_index", 0},
164+ {"byte_size", 2},
165+ {"file", "${resource}/resources/include/constants.h"},
166+ {"template", "@@STATIC_TILING@@"}}})},
167+ {"link_options", Json::array({"-m", "aicorelinux", "-Ttext=0"})},
168+ {"objects", Json::array(
169+ {{{"object_name", "basic"},
170+ {"object_type", "basic"},
171+ {"commands", Json::array(
172+ {{{"type", "compile"},
173+ {"stage", 0},
174+ {"cmd", Json::array(
175+ {"${env:ASCEND_HOME_PATH}/bin/bisheng",
176+ "${resource}/resources/src/basic.cpp",
177+ "-o", "${output}/basic.o"})}}})},
178+ {"outputs", Json::array({"${output}/basic.o"})}}})}}})}};
179+ }
180+ 
181+ fs::path rootPath_;
182+ fs::path worktreePath_;
183+ fs::path sourceDirectoryPath_;
184+ fs::path toolchainPath_;
185+ fs::path outputElfPath_;
186+ Json manifest_;
187+};
188+ 
189+class ScopedRegisteredCompilationResource final {
190+public:
191+ ScopedRegisteredCompilationResource(
192+ std::string resourceId, const Json& manifest, const fs::path& sourceDirectoryPath,
193+ std::vector<ResourceFileData> resourceFiles = {{"payload.bin", "payload.bin", {0x01U, 0x02U}}})
194+ : registry_(ResourceRegistry::Instance()),
195+ resourceId_(std::move(resourceId)),
196+ previousTemporaryRoot_(registry_.temporaryRoot_),
197+ previousAutomaticLoadAttempted_(registry_.automaticLoadAttempted_),
198+ previousKeepTemporaryRoot_(registry_.keepTemporaryRoot_),
199+ previousAutomaticLoadStatus_(registry_.automaticLoadStatus_)
200+ {
201+ registry_.temporaryRoot_.clear();
202+ registry_.automaticLoadAttempted_ = true;
203+ registry_.keepTemporaryRoot_ = false;
204+ registry_.automaticLoadStatus_ = ResourceStatus::Success;
205+ 
206+ std::unique_ptr<ResourceEntry> resourceEntry(new ResourceEntry());
207+ resourceEntry->data.json = manifest;
208+ resourceEntry->data.sourceFilePath = sourceDirectoryPath.string();
209+ resourceEntry->files = std::move(resourceFiles);
210+ resourceEntry->sourceType = ResourceSourceType::External;
211+ inserted_ = registry_.externalResources_.emplace(resourceId_, std::move(resourceEntry)).second;
212+ }
213+ 
214+ ~ScopedRegisteredCompilationResource()
215+ {
216+ if (inserted_) {
217+ registry_.externalResources_.erase(resourceId_);
218+ }
219+ boost::system::error_code ignoredError;
220+ fs::remove_all(registry_.temporaryRoot_, ignoredError);
221+ registry_.temporaryRoot_ = previousTemporaryRoot_;
222+ registry_.automaticLoadAttempted_ = previousAutomaticLoadAttempted_;
223+ registry_.keepTemporaryRoot_ = previousKeepTemporaryRoot_;
224+ registry_.automaticLoadStatus_ = previousAutomaticLoadStatus_;
225+ }
226+ 
227+ bool WasInserted() const noexcept { return inserted_; }
228+ 
229+ void SetAutomaticLoadStatus(ResourceStatus loadStatus) noexcept { registry_.automaticLoadStatus_ = loadStatus; }
230+ 
231+private:
232+ ResourceRegistry& registry_;
233+ std::string resourceId_;
234+ std::string previousTemporaryRoot_;
235+ bool previousAutomaticLoadAttempted_{false};
236+ bool previousKeepTemporaryRoot_{false};
237+ ResourceStatus previousAutomaticLoadStatus_{ResourceStatus::Success};
238+ bool inserted_{false};
239+};
240+ 
241+aclrtcKernelSpecRequest CreateRequest(const uint64_t* constantByteCounts, const void* const* constantAddresses)
242+{
243+ aclrtcKernelSpecRequest request{};
244+ request.resourceId = RESOURCE_ID;
245+ request.kernelEntry = "add_custom_100000";
246+ request.argsCount = 1;
247+ request.argsAddr = constantAddresses;
248+ request.argsBytes = constantByteCounts;
249+ return request;
250+}
251+ 
252+NormalizedKernelSpecializationRequest NormalizeRequestForTest(
253+ const aclrtcKernelSpecRequest& request, const fs::path& outputElfPath)
254+{
255+ NormalizedKernelSpecializationRequest normalizedRequest;
256+ EXPECT_EQ(
257+ ascendc::aclrtc::NormalizeKernelSpecializationRequest(request, outputElfPath.c_str(), normalizedRequest),
258+ ascendc::aclrtc::ACLRTC_SUCCESS);
259+ return normalizedRequest;
260+}
261+ 
262+std::string ReadFile(const fs::path& path)
263+{
264+ std::ifstream stream(path.string());
265+ return std::string(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
266+}
267+ 
268+TEST(KernelSpecializationSessionTest, RunsPatchCompileLinkPublishAndCleanupWithMaterializedResource)
269+{
270+ EndToEndTestResource testResource(FakeCompilerOutcome::Success);
271+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", testResource.ToolchainPath().c_str());
272+ ScopedEnvironmentVariable saveMetaEnvironment("ASCEND_OP_COMPILE_SAVE_KERNEL_META", nullptr);
273+ const uint8_t constantBytes[] = {0x01, 0xfe};
274+ const void* constantAddresses[] = {constantBytes};
275+ const uint64_t constantByteCounts[] = {sizeof(constantBytes)};
276+ aclrtcKernelSpecRequest request = CreateRequest(constantByteCounts, constantAddresses);
277+ 
278+ KernelSpecializationSession session;
279+ EXPECT_EQ(
280+ session.RunSpecializationWithMaterializedResource(
281+ NormalizeRequestForTest(request, testResource.OutputElfPath()),
282+ testResource.CreateMaterializedCompilationResource()),
283+ 0);
284+ EXPECT_EQ(ReadFile(testResource.OutputElfPath()), "linked-elf");
285+ EXPECT_FALSE(fs::exists(testResource.WorktreePath()));
286+}
287+ 
288+TEST(KernelSpecializationSessionTest, DoesNotReplaceExistingOutputWhenCompilationFails)
289+{
290+ EndToEndTestResource testResource(FakeCompilerOutcome::Failure);
291+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", testResource.ToolchainPath().c_str());
292+ ScopedEnvironmentVariable saveMetaEnvironment("ASCEND_OP_COMPILE_SAVE_KERNEL_META", nullptr);
293+ std::ofstream(testResource.OutputElfPath().string()) << "previous-elf";
294+ const uint8_t constantBytes[] = {0x01, 0xfe};
295+ const void* constantAddresses[] = {constantBytes};
296+ const uint64_t constantByteCounts[] = {sizeof(constantBytes)};
297+ aclrtcKernelSpecRequest request = CreateRequest(constantByteCounts, constantAddresses);
298+ 
299+ KernelSpecializationSession session;
300+ EXPECT_EQ(
301+ session.RunSpecializationWithMaterializedResource(
302+ NormalizeRequestForTest(request, testResource.OutputElfPath()),
303+ testResource.CreateMaterializedCompilationResource()),
304+ ascendc::aclrtc::ACLRTC_ERROR_COMPILATION);
305+ EXPECT_EQ(ReadFile(testResource.OutputElfPath()), "previous-elf");
306+ EXPECT_FALSE(fs::exists(testResource.WorktreePath()));
307+}
308+ 
309+TEST(KernelSpecializationSessionTest, FailsWhenOutputDirectoryIsMissing)
310+{
311+ EndToEndTestResource testResource(FakeCompilerOutcome::Success);
312+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", testResource.ToolchainPath().c_str());
313+ ScopedEnvironmentVariable saveMetaEnvironment("ASCEND_OP_COMPILE_SAVE_KERNEL_META", nullptr);
314+ boost::system::error_code removeError;
315+ fs::remove_all(testResource.OutputElfPath().parent_path(), removeError);
316+ ASSERT_FALSE(removeError);
317+ const uint8_t constantBytes[] = {0x01, 0xfe};
318+ const void* constantAddresses[] = {constantBytes};
319+ const uint64_t constantByteCounts[] = {sizeof(constantBytes)};
320+ aclrtcKernelSpecRequest request = CreateRequest(constantByteCounts, constantAddresses);
321+ 
322+ KernelSpecializationSession session;
323+ EXPECT_EQ(
324+ session.RunSpecializationWithMaterializedResource(
325+ NormalizeRequestForTest(request, testResource.OutputElfPath()),
326+ testResource.CreateMaterializedCompilationResource()),
327+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
328+}
329+ 
330+TEST(KernelSpecializationSessionTest, RetainsWorktreeWhenKernelMetaSavingValueHasWhitespace)
331+{
332+ EndToEndTestResource testResource(FakeCompilerOutcome::Success);
333+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", testResource.ToolchainPath().c_str());
334+ ScopedEnvironmentVariable saveMetaEnvironment("ASCEND_OP_COMPILE_SAVE_KERNEL_META", " \t1\r\n");
335+ const uint8_t constantBytes[] = {0x01, 0xfe};
336+ const void* constantAddresses[] = {constantBytes};
337+ const uint64_t constantByteCounts[] = {sizeof(constantBytes)};
338+ aclrtcKernelSpecRequest request = CreateRequest(constantByteCounts, constantAddresses);
339+ 
340+ KernelSpecializationSession session;
341+ EXPECT_EQ(
342+ session.RunSpecializationWithMaterializedResource(
343+ NormalizeRequestForTest(request, testResource.OutputElfPath()),
344+ testResource.CreateMaterializedCompilationResource()),
345+ 0);
346+ EXPECT_TRUE(fs::is_directory(testResource.WorktreePath()));
347+ EXPECT_TRUE(fs::is_regular_file(testResource.WorktreePath() / "aclrtc_manifest.json"));
348+ EXPECT_TRUE(fs::is_regular_file(testResource.WorktreePath() / "aclrtc_compile.log"));
349+ EXPECT_TRUE(fs::is_regular_file(testResource.WorktreePath() / "aclrtc_replay.sh"));
350+ EXPECT_TRUE(fs::is_regular_file(testResource.WorktreePath() / "aclrtc_result.json"));
351+ EXPECT_NE(
352+ ReadFile(testResource.WorktreePath() / "aclrtc_replay.sh").find(testResource.WorktreePath().string()),
353+ std::string::npos);
354+}
355+ 
356+TEST(KernelSpecializationSessionTest, LoadsAndCopiesFreshMaterializedPathsFromRegistry)
357+{
358+ const fs::path testRoot = fs::temp_directory_path() / fs::unique_path("aclrtc_session_registry_%%%%-%%%%-%%%%");
359+ const fs::path sourceDirectoryPath = testRoot / "source";
360+ fs::create_directories(sourceDirectoryPath);
361+ ScopedEnvironmentVariable temporaryDirectoryEnvironment("TMPDIR", testRoot.c_str());
362+ const std::string resourceId = "aclrtc-session-registry-integration";
363+ const Json manifest = {{"resource_id", resourceId}, {"schema_version", "1.0"}};
364+ ScopedRegisteredCompilationResource registeredResource(resourceId, manifest, sourceDirectoryPath);
365+ ASSERT_TRUE(registeredResource.WasInserted());
366+ 
367+ registeredResource.SetAutomaticLoadStatus(ResourceStatus::NotFound);
368+ MaterializedKernelCompilationResource rejectedResource;
369+ EXPECT_EQ(
370+ KernelSpecializationSession::PrepareCompilationResource(resourceId, rejectedResource),
371+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
372+ 
373+ registeredResource.SetAutomaticLoadStatus(ResourceStatus::Success);
374+ MaterializedKernelCompilationResource firstResource;
375+ ASSERT_EQ(
376+ KernelSpecializationSession::PrepareCompilationResource(resourceId, firstResource),
377+ ascendc::aclrtc::ACLRTC_SUCCESS);
378+ EXPECT_EQ(firstResource.manifest, manifest);
379+ EXPECT_EQ(firstResource.externalSourceDirectoryPath, sourceDirectoryPath);
380+ EXPECT_TRUE(fs::is_regular_file(firstResource.ownedResourceDirectoryPath / "payload.bin"));
381+ 
382+ MaterializedKernelCompilationResource secondResource;
383+ ASSERT_EQ(
384+ KernelSpecializationSession::PrepareCompilationResource(resourceId, secondResource),
385+ ascendc::aclrtc::ACLRTC_SUCCESS);
386+ EXPECT_NE(firstResource.ownedResourceDirectoryPath, secondResource.ownedResourceDirectoryPath);
387+ EXPECT_TRUE(fs::is_regular_file(secondResource.ownedResourceDirectoryPath / "payload.bin"));
388+}
389+ 
390+TEST(KernelSpecializationApiTest, ReportsMissingRegisteredResource)
391+{
392+ const fs::path testRoot = fs::temp_directory_path() / fs::unique_path("aclrtc_session_missing_resource_%%%%-%%%%");
393+ fs::create_directories(testRoot);
394+ ScopedRegisteredCompilationResource registeredResource(
395+ "unrelated-resource", Json{{"schema_version", "1.0"}}, testRoot);
396+ ASSERT_TRUE(registeredResource.WasInserted());
397+ const uint8_t constantBytes[] = {0x01, 0xfe};
398+ const void* constantAddresses[] = {constantBytes};
399+ const uint64_t constantByteCounts[] = {sizeof(constantBytes)};
400+ aclrtcKernelSpecRequest request = CreateRequest(constantByteCounts, constantAddresses);
401+ request.resourceId = "missing-resource";
402+ 
403+ EXPECT_EQ(
404+ aclrtcKernelSpecialization(&request, (testRoot / "kernel.elf").c_str()), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
405+ 
406+ boost::system::error_code ignoredError;
407+ fs::remove_all(testRoot, ignoredError);
408+}
409+ 
410+TEST(KernelSpecializationApiTest, RejectsNullPointersBeforeResourceLookup)
411+{
412+ aclrtcKernelSpecRequest request{};
413+ EXPECT_EQ(aclrtcKernelSpecialization(nullptr, "/tmp/kernel.elf"), ascendc::aclrtc::ACLRTC_ERROR_INVALID_INPUT);
414+ 
415+ EXPECT_EQ(aclrtcKernelSpecialization(&request, nullptr), ascendc::aclrtc::ACLRTC_ERROR_INVALID_INPUT);
416+ 
417+ EXPECT_EQ(aclrtcKernelSpecialization(&request, "/tmp/kernel.elf"), ascendc::aclrtc::ACLRTC_ERROR_INVALID_INPUT);
418+}
419+ 
420+TEST(KernelSpecializationApiTest, CompilesRegisteredResourceThroughPublicApi)
421+{
422+ EndToEndTestResource testResource(FakeCompilerOutcome::Success);
423+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", testResource.ToolchainPath().c_str());
424+ ScopedEnvironmentVariable saveMetaEnvironment("ASCEND_OP_COMPILE_SAVE_KERNEL_META", nullptr);
425+ const std::vector<ResourceFileData> resourceFiles = {
426+ {"constants.h", "resources/include/constants.h", {'T', 'I', 'L', 'I', 'N', 'G', '=', '@', '@',
427+ 'S', 'T', 'A', 'T', 'I', 'C', '_', 'T', 'I',
428+ 'L', 'I', 'N', 'G', '@', '@', ';', '\n'}},
429+ {"basic.cpp", "resources/src/basic.cpp", {'/', '/', ' ', 'b', 'a', 's', 'i', 'c', '\n'}},
430+ };
431+ ScopedRegisteredCompilationResource registeredResource(
432+ RESOURCE_ID, testResource.Manifest(), testResource.SourceDirectoryPath(), resourceFiles);
433+ ASSERT_TRUE(registeredResource.WasInserted());
434+ const uint8_t constantBytes[] = {0x01, 0xfe};
435+ const void* constantAddresses[] = {constantBytes};
436+ const uint64_t constantByteCounts[] = {sizeof(constantBytes)};
437+ aclrtcKernelSpecRequest request = CreateRequest(constantByteCounts, constantAddresses);
438+ 
439+ EXPECT_EQ(
440+ aclrtcKernelSpecialization(&request, testResource.OutputElfPath().c_str()), ascendc::aclrtc::ACLRTC_SUCCESS);
441+ EXPECT_EQ(ReadFile(testResource.OutputElfPath()), "linked-elf");
442+}
443+ 
444+TEST(KernelSpecializationSessionTest, ReportsManifestPlanFailure)
445+{
446+ EndToEndTestResource testResource(FakeCompilerOutcome::Success);
447+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", testResource.ToolchainPath().c_str());
448+ ScopedEnvironmentVariable saveMetaEnvironment("ASCEND_OP_COMPILE_SAVE_KERNEL_META", nullptr);
449+ const uint8_t constantBytes[] = {0x01, 0xfe};
450+ const void* constantAddresses[] = {constantBytes};
451+ const uint64_t constantByteCounts[] = {sizeof(constantBytes)};
452+ const aclrtcKernelSpecRequest request = CreateRequest(constantByteCounts, constantAddresses);
453+ MaterializedKernelCompilationResource resource = testResource.CreateMaterializedCompilationResource();
454+ resource.manifest["schema_version"] = "unsupported";
455+ 
456+ KernelSpecializationSession session;
457+ EXPECT_EQ(
458+ session.RunSpecializationWithMaterializedResource(
459+ NormalizeRequestForTest(request, testResource.OutputElfPath()), std::move(resource)),
460+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
461+ EXPECT_FALSE(fs::exists(testResource.OutputElfPath()));
462+}
463+ 
464+TEST(KernelSpecializationSessionTest, ReportsSourcePatchFailure)
465+{
466+ EndToEndTestResource testResource(FakeCompilerOutcome::Success);
467+ ScopedEnvironmentVariable ascendHomeEnvironment("ASCEND_HOME_PATH", testResource.ToolchainPath().c_str());
468+ ScopedEnvironmentVariable saveMetaEnvironment("ASCEND_OP_COMPILE_SAVE_KERNEL_META", nullptr);
469+ const uint8_t constantBytes[] = {0x01, 0xfe};
470+ const void* constantAddresses[] = {constantBytes};
471+ const uint64_t constantByteCounts[] = {sizeof(constantBytes)};
472+ const aclrtcKernelSpecRequest request = CreateRequest(constantByteCounts, constantAddresses);
473+ MaterializedKernelCompilationResource resource = testResource.CreateMaterializedCompilationResource();
474+ resource.manifest["kernels"][0]["constant_infos"][0]["template"] = "@@MISSING_TEMPLATE@@";
475+ 
476+ KernelSpecializationSession session;
477+ EXPECT_EQ(
478+ session.RunSpecializationWithMaterializedResource(
479+ NormalizeRequestForTest(request, testResource.OutputElfPath()), std::move(resource)),
480+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
481+ EXPECT_FALSE(fs::exists(testResource.OutputElfPath()));
482+}
483+ 
484+TEST(KernelSpecializationSessionTest, RejectsMissingLinkedElfDuringPublication)
485+{
486+ const fs::path testRoot = fs::temp_directory_path() / fs::unique_path("aclrtc_publish_missing_%%%%-%%%%");
487+ const fs::path worktreePath = testRoot / "worktree";
488+ fs::create_directories(worktreePath);
489+ KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
490+ KernelSpecializationSession session;
491+ 
492+ EXPECT_EQ(
493+ session.PublishKernelElf(testRoot / "missing.elf", testRoot / "output.elf", workspace),
494+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
495+ 
496+ boost::system::error_code ignoredError;
497+ fs::remove_all(testRoot, ignoredError);
498+}
499+ 
500+TEST(KernelSpecializationSessionTest, RemovesTemporaryElfWhenPublicationTargetIsDirectory)
501+{
502+ const fs::path testRoot = fs::temp_directory_path() / fs::unique_path("aclrtc_publish_directory_%%%%-%%%%");
503+ const fs::path worktreePath = testRoot / "worktree";
504+ const fs::path linkedElfPath = testRoot / "linked.elf";
505+ const fs::path outputDirectoryPath = testRoot / "output.elf";
506+ fs::create_directories(worktreePath);
507+ fs::create_directories(outputDirectoryPath);
508+ std::ofstream(linkedElfPath.string()) << "linked-elf";
509+ KernelCompilationWorkspace workspace(worktreePath, WorktreeRetentionPolicy::RetainAfterCompilation);
510+ KernelSpecializationSession session;
511+ 
512+ EXPECT_EQ(
513+ session.PublishKernelElf(linkedElfPath, outputDirectoryPath, workspace), ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
514+ EXPECT_TRUE(fs::is_directory(outputDirectoryPath));
515+ EXPECT_EQ(std::distance(fs::directory_iterator(testRoot), fs::directory_iterator()), 3);
516+ 
517+ boost::system::error_code ignoredError;
518+ fs::remove_all(testRoot, ignoredError);
519+}
520+ 
521+TEST(KernelSpecializationSessionTest, RemovesTemporaryElfWhenLinkedElfCannotBeRead)
522+{
523+ const fs::path testRoot = fs::temp_directory_path() / fs::unique_path("aclrtc_publish_read_failure_%%%%-%%%%");
524+ const fs::path linkedElfDirectory = testRoot / "linked.elf";
525+ fs::create_directories(linkedElfDirectory);
526+ KernelCompilationWorkspace workspace(testRoot / "worktree", WorktreeRetentionPolicy::RetainAfterCompilation);
527+ KernelSpecializationSession session;
528+ 
529+ EXPECT_EQ(
530+ session.PublishKernelElf(linkedElfDirectory, testRoot / "output.elf", workspace),
531+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
532+ EXPECT_EQ(std::distance(fs::directory_iterator(testRoot), fs::directory_iterator()), 1);
533+ 
534+ boost::system::error_code ignoredError;
535+ fs::remove_all(testRoot, ignoredError);
536+}
537+ 
538+TEST(KernelSpecializationSessionTest, RemovesTemporaryElfWhenOutputWriteFails)
539+{
540+ const fs::path testRoot = fs::temp_directory_path() / fs::unique_path("aclrtc_publish_write_failure_%%%%-%%%%");
541+ fs::create_directories(testRoot);
542+ const fs::path linkedElfPath = testRoot / "linked.elf";
543+ std::ofstream(linkedElfPath.string()) << "linked-elf";
544+ KernelCompilationWorkspace workspace(testRoot / "worktree", WorktreeRetentionPolicy::RetainAfterCompilation);
545+ KernelSpecializationSession session;
546+ struct rlimit originalFileSizeLimit {};
547+ ASSERT_EQ(getrlimit(RLIMIT_FSIZE, &originalFileSizeLimit), 0);
548+ struct sigaction ignoreFileSizeSignal {};
549+ struct sigaction originalFileSizeSignal {};
550+ ignoreFileSizeSignal.sa_handler = SIG_IGN;
551+ ASSERT_EQ(sigemptyset(&ignoreFileSizeSignal.sa_mask), 0);
552+ ASSERT_EQ(sigaction(SIGXFSZ, &ignoreFileSizeSignal, &originalFileSizeSignal), 0);
553+ struct rlimit zeroFileSizeLimit = originalFileSizeLimit;
554+ zeroFileSizeLimit.rlim_cur = 0U;
555+ ASSERT_EQ(setrlimit(RLIMIT_FSIZE, &zeroFileSizeLimit), 0);
556+ 
557+ const aclError publishResult = session.PublishKernelElf(linkedElfPath, testRoot / "output.elf", workspace);
558+ 
559+ const int restoreLimitResult = setrlimit(RLIMIT_FSIZE, &originalFileSizeLimit);
560+ const int restoreSignalResult = sigaction(SIGXFSZ, &originalFileSizeSignal, nullptr);
561+ ASSERT_EQ(restoreLimitResult, 0);
562+ ASSERT_EQ(restoreSignalResult, 0);
563+ EXPECT_EQ(publishResult, ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
564+ EXPECT_FALSE(fs::exists(testRoot / "output.elf"));
565+ EXPECT_EQ(std::distance(fs::directory_iterator(testRoot), fs::directory_iterator()), 1);
566+ 
567+ boost::system::error_code ignoredError;
568+ fs::remove_all(testRoot, ignoredError);
569+}
570+ 
571+TEST(KernelSpecializationSessionTest, StopsPublicationWhenWorktreeCannotBeRemoved)
572+{
573+ const fs::path testRoot = fs::temp_directory_path() / fs::unique_path("aclrtc_publish_cleanup_failure_%%%%-%%%%");
574+ fs::create_directories(testRoot);
575+ const fs::path linkedElfPath = testRoot / "linked.elf";
576+ std::ofstream(linkedElfPath.string()) << "linked-elf";
577+ KernelCompilationWorkspace workspace("/proc/self/status", WorktreeRetentionPolicy::RemoveAfterCompilation);
578+ KernelSpecializationSession session;
579+ 
580+ EXPECT_EQ(
581+ session.PublishKernelElf(linkedElfPath, testRoot / "output.elf", workspace),
582+ ascendc::aclrtc::ACLRTC_ERROR_FAILURE);
583+ EXPECT_FALSE(fs::exists(testRoot / "output.elf"));
584+ EXPECT_EQ(std::distance(fs::directory_iterator(testRoot), fs::directory_iterator()), 1);
585+ 
586+ boost::system::error_code ignoredError;
587+ fs::remove_all(testRoot, ignoredError);
588+}
589+} // namespace
@@ -12,12 +12,15 @@
12#include <mockcpp/mockcpp.hpp>12#include <mockcpp/mockcpp.hpp>
13 13 
14#include <cstdlib>14#include <cstdlib>
15+#include <csignal>
15#include <dlfcn.h>16#include <dlfcn.h>
16#include <fstream>17#include <fstream>
17#include <iterator>18#include <iterator>
18#include <memory>19#include <memory>
19#include <set>20#include <set>
20#include <string>21#include <string>
22+#include <sys/resource.h>
23+#include <type_traits>
21#include <utility>24#include <utility>
22#include <vector>25#include <vector>
23 26 
@@ -42,6 +45,12 @@ constexpr uint64_t TEST_MAX_EXTENSION_COUNT = 4096U;
42constexpr uint64_t TEST_MAX_FILE_COUNT = 65536U;45constexpr uint64_t TEST_MAX_FILE_COUNT = 65536U;
43constexpr uint64_t TEST_MAX_REGISTRY_FILE_COUNT = 131072U;46constexpr uint64_t TEST_MAX_REGISTRY_FILE_COUNT = 131072U;
44 47 
48+static_assert(
49+ std::is_same<std::underlying_type_t<ResourceStatus>, uint32_t>::value, "ResourceStatus must have a stable width");
50+static_assert(
51+ std::is_same<std::underlying_type_t<ResourceSourceType>, uint32_t>::value,
52+ "ResourceSourceType must have a stable width");
53+ 
45const AcCompileResourceBundleHeader* gBundleHeader = nullptr;54const AcCompileResourceBundleHeader* gBundleHeader = nullptr;
46uint32_t gDlopenCalls = 0U;55uint32_t gDlopenCalls = 0U;
47uint32_t gDlsymCalls = 0U;56uint32_t gDlsymCalls = 0U;
@@ -1111,6 +1120,30 @@ TEST_F(ResourceRegistryTest, WriteMaterializedFilesWritesNestedAndEmptyPayloadsA
1111 EXPECT_EQ(ResourceRegistry::WriteMaterializedFiles(direct, directoryTarget.string()), ResourceStatus::IoError);1120 EXPECT_EQ(ResourceRegistry::WriteMaterializedFiles(direct, directoryTarget.string()), ResourceStatus::IoError);
1112}1121}
1113 1122 
1123+TEST_F(ResourceRegistryTest, ReportsMaterializedFileFinalizeFailure)
1124+{
1125+ struct rlimit originalFileSizeLimit {};
1126+ ASSERT_EQ(getrlimit(RLIMIT_FSIZE, &originalFileSizeLimit), 0);
1127+ struct sigaction ignoreFileSizeSignal {};
1128+ struct sigaction originalFileSizeSignal {};
1129+ ignoreFileSizeSignal.sa_handler = SIG_IGN;
1130+ ASSERT_EQ(sigemptyset(&ignoreFileSizeSignal.sa_mask), 0);
1131+ ASSERT_EQ(sigaction(SIGXFSZ, &ignoreFileSizeSignal, &originalFileSizeSignal), 0);
1132+ struct rlimit zeroFileSizeLimit = originalFileSizeLimit;
1133+ zeroFileSizeLimit.rlim_cur = 0U;
1134+ ASSERT_EQ(setrlimit(RLIMIT_FSIZE, &zeroFileSizeLimit), 0);
1135+ 
1136+ const fs::path output = Path("materialized-finalize-failure");
1137+ const std::vector<ResourceFileData> files = {{"data.bin", "data.bin", {1U}}};
1138+ const ResourceStatus writeStatus = ResourceRegistry::WriteMaterializedFiles(files, output.string());
1139+ 
1140+ const int restoreLimitResult = setrlimit(RLIMIT_FSIZE, &originalFileSizeLimit);
1141+ const int restoreSignalResult = sigaction(SIGXFSZ, &originalFileSizeSignal, nullptr);
1142+ ASSERT_EQ(restoreLimitResult, 0);
1143+ ASSERT_EQ(restoreSignalResult, 0);
1144+ EXPECT_EQ(writeStatus, ResourceStatus::IoError);
1145+}
1146+ 
1114TEST_F(ResourceRegistryTest, LookupValidatesInputsAndReturnsIndependentMaterializations)1147TEST_F(ResourceRegistryTest, LookupValidatesInputsAndReturnsIndependentMaterializations)
1115{1148{
1116 ResourceData output;1149 ResourceData output;
@@ -8,10 +8,11 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------------------------------------9# ----------------------------------------------------------------------------------------------------------
10aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/ SRC)10aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/ SRC)
11+aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/specialization SPECIALIZATION_SRC)
11 12 
12add_library(acl_rtc SHARED13add_library(acl_rtc SHARED
13 ${SRC}14 ${SRC}
14- ${CMAKE_CURRENT_SOURCE_DIR}/specialization/resource_registry.cpp15+ ${SPECIALIZATION_SRC}
15 ${CMAKE_CURRENT_SOURCE_DIR}/../build/common/file_utils.cpp16 ${CMAKE_CURRENT_SOURCE_DIR}/../build/common/file_utils.cpp
16)17)
17 18 
@@ -58,8 +59,14 @@ target_link_libraries(acl_rtc PRIVATE
58 json59 json
59)60)
60 61 
62+target_compile_definitions(acl_rtc PRIVATE
63+ ACLRTC_FUNC_VISIBILITY
64+)
65+ 
61target_compile_options(acl_rtc PRIVATE66target_compile_options(acl_rtc PRIVATE
62 -fPIE67 -fPIE
68+ -fvisibility=hidden
69+ -fvisibility-inlines-hidden
63 -fno-strict-aliasing70 -fno-strict-aliasing
64 -fno-common71 -fno-common
65 -Wall72 -Wall
@@ -74,6 +81,7 @@ target_compile_options(acl_rtc PRIVATE
74target_link_options(acl_rtc PRIVATE81target_link_options(acl_rtc PRIVATE
75 -Wl,-z,relro,-z,now,-z,noexecstack82 -Wl,-z,relro,-z,now,-z,noexecstack
76 -Wl,-Bsymbolic83 -Wl,-Bsymbolic
84+ -Wl,--exclude-libs,ALL
77 -Wl,--gc-sections85 -Wl,--gc-sections
78 $<$<CONFIG:Release>:-s>86 $<$<CONFIG:Release>:-s>
79)87)