@@ -344,13 +344,16 @@ auto FileTestBase::ProcessTestFileAndRun(TestContext& context)
344344 llvm::PrettyStackTraceProgram stack_trace_entry (
345345 test_argv_for_stack_trace.size () - 1 , test_argv_for_stack_trace.data ());
346346
347+ // Execution must be serialized for either serial tests or console output.
348+ std::unique_lock<std::mutex> output_lock;
349+ if (output_mutex_ &&
350+ (context.capture_console_output || !AllowParallelRun ())) {
351+ output_lock = std::unique_lock<std::mutex>(*output_mutex_);
352+ }
353+
347354 // Conditionally capture console output. We use a scope exit to ensure the
348355 // captures terminate even on run failures.
349- std::unique_lock<std::mutex> output_lock;
350356 if (context.capture_console_output ) {
351- if (output_mutex_) {
352- output_lock = std::unique_lock<std::mutex>(*output_mutex_);
353- }
354357 CaptureStderr ();
355358 CaptureStdout ();
356359 }
@@ -514,6 +517,84 @@ struct SplitState {
514517 int file_index = 0 ;
515518};
516519
520+ // Replaces the keyword at the given position. Returns the position to start a
521+ // find for the next keyword.
522+ static auto ReplaceContentKeywordAt (std::string* content, size_t keyword_pos,
523+ llvm::StringRef test_name, int * lsp_id)
524+ -> ErrorOr<size_t> {
525+ auto keyword = llvm::StringRef (*content).substr (keyword_pos);
526+
527+ // Line replacements aren't handled here.
528+ static constexpr llvm::StringLiteral Line = " [[@LINE" ;
529+ if (keyword.starts_with (Line)) {
530+ // Just move past the prefix to find the next one.
531+ return keyword_pos + Line.size ();
532+ }
533+
534+ // Replaced with the actual test name.
535+ static constexpr llvm::StringLiteral TestName = " [[@TEST_NAME]]" ;
536+ if (keyword.starts_with (TestName)) {
537+ content->replace (keyword_pos, TestName.size (), test_name);
538+ return keyword_pos + test_name.size ();
539+ }
540+
541+ // Reformatted as an LSP call with headers.
542+ static constexpr llvm::StringLiteral Lsp = " [[@LSP:" ;
543+ if (keyword.starts_with (Lsp)) {
544+ auto method_start = keyword_pos + Lsp.size ();
545+
546+ static constexpr llvm::StringLiteral LspEnd = " ]]" ;
547+ auto keyword_end = content->find (" ]]" , method_start);
548+ if (keyword_end == std::string::npos) {
549+ return ErrorBuilder ()
550+ << " Missing `" << LspEnd << " ` after `" << Lsp << " `" ;
551+ }
552+
553+ auto method_end = content->find (" :" , method_start);
554+ auto extra_content_start = method_end + 1 ;
555+ if (method_end == std::string::npos || method_end > keyword_end) {
556+ method_end = keyword_end;
557+ extra_content_start = keyword_end;
558+ }
559+ auto method = content->substr (method_start, method_end - method_start);
560+
561+ auto extra_content =
562+ content->substr (extra_content_start, keyword_end - extra_content_start);
563+ std::string extra_content_sep;
564+ if (!extra_content.empty ()) {
565+ extra_content_sep = " ," ;
566+ if (!extra_content.starts_with (" \n " )) {
567+ extra_content_sep += " " ;
568+ }
569+ }
570+
571+ // Form the JSON.
572+ std::string json;
573+ if (method == " exit" ) {
574+ if (!extra_content.empty ()) {
575+ return Error (" `[[@LSP:exit:` cannot include extra content" );
576+ }
577+ json = R"( {"jsonrpc": "2.0", "method": "exit"})" ;
578+ } else {
579+ json = llvm::formatv (
580+ R"( {{"jsonrpc": "2.0", "id": "{0}", "method": "{1}"{2}{3}})" ,
581+ ++(*lsp_id), method, extra_content_sep, extra_content)
582+ .str ();
583+ }
584+ // Add the Content-Length header. The `2` accounts for extra newlines.
585+ auto json_with_header =
586+ llvm::formatv (" Content-Length: {0}\n\n {1}\n " , json.size () + 2 , json)
587+ .str ();
588+ // Insert the content.
589+ content->replace (keyword_pos, keyword_end + 2 - keyword_pos,
590+ json_with_header);
591+ return keyword_pos + json_with_header.size ();
592+ }
593+
594+ return ErrorBuilder () << " Unexpected use of `[[@` at `"
595+ << keyword.substr (0 , 5 ) << " `" ;
596+ }
597+
517598// Replaces the content keywords.
518599//
519600// TEST_NAME is the only content keyword at present, but we do validate that
@@ -543,20 +624,13 @@ static auto ReplaceContentKeywords(llvm::StringRef filename,
543624 test_name.consume_front (" fail_" );
544625 test_name.consume_front (" todo_" );
545626
627+ // A counter for LSP calls.
628+ int lsp_id = 0 ;
546629 while (keyword_pos != std::string::npos) {
547- static constexpr llvm::StringLiteral TestName = " [[@TEST_NAME]]" ;
548- auto keyword = llvm::StringRef (*content).substr (keyword_pos);
549- if (keyword.starts_with (TestName)) {
550- content->replace (keyword_pos, TestName.size (), test_name);
551- keyword_pos += test_name.size ();
552- } else if (keyword.starts_with (" [[@LINE" )) {
553- // Just move past the prefix to find the next one.
554- keyword_pos += Prefix.size ();
555- } else {
556- return ErrorBuilder ()
557- << " Unexpected use of `[[@` at `" << keyword.substr (0 , 5 ) << " `" ;
558- }
559- keyword_pos = content->find (Prefix, keyword_pos);
630+ CARBON_ASSIGN_OR_RETURN (
631+ auto keyword_end,
632+ ReplaceContentKeywordAt (content, keyword_pos, test_name, &lsp_id));
633+ keyword_pos = content->find (Prefix, keyword_end);
560634 }
561635 return Success ();
562636}
0 commit comments