Skip to content

Commit 087cf38

Browse files
committed
fix(github): do not espace trailing newlines in logger
1 parent a7290be commit 087cf38

1 file changed

Lines changed: 66 additions & 6 deletions

File tree

  • src/run/run_environment/github_actions

src/run/run_environment/github_actions/logger.rs

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ impl Log for GithubActionLogger {
5656
}
5757

5858
if let Some(announcement) = get_announcement_event(record) {
59-
// properly escape newlines so that GitHub Actions interprets them correctly
60-
// https://github.com/actions/toolkit/issues/193#issuecomment-605394935
61-
let escaped_announcement = announcement.replace('\n', "%0A");
59+
let escaped_announcement = escape_multiline_message(&announcement);
6260
// TODO: make the announcement title configurable
6361
println!("::notice title=New CodSpeed Feature::{escaped_announcement}");
6462
return;
@@ -79,9 +77,7 @@ impl Log for GithubActionLogger {
7977
Level::Debug => "::debug::",
8078
Level::Trace => "::debug::[TRACE]",
8179
};
82-
// properly escape newlines so that GitHub Actions interprets them correctly
83-
// https://github.com/actions/toolkit/issues/193#issuecomment-605394935
84-
let message_string = message.to_string().replace('\n', "%0A");
80+
let message_string = escape_multiline_message(&message.to_string());
8581
println!("{prefix}{message_string}");
8682
}
8783

@@ -103,3 +99,67 @@ impl SharedLogger for GithubActionLogger {
10399
Box::new(*self)
104100
}
105101
}
102+
103+
/// Escapes newlines in a message for GitHub Actions logging.
104+
/// GitHub Actions requires newlines to be replaced with `%0A` to be interpreted correctly.
105+
///
106+
/// See https://github.com/actions/toolkit/issues/193#issuecomment-605394935
107+
///
108+
/// One exception: trailing newlines are preserved as actual newlines to maintain formatting.
109+
fn escape_multiline_message(message: &str) -> String {
110+
let trailing_newlines = message.len() - message.trim_end_matches('\n').len();
111+
if trailing_newlines > 0 {
112+
let stripped = &message[..message.len() - trailing_newlines];
113+
let escaped = stripped.replace('\n', "%0A");
114+
let newlines = "\n".repeat(trailing_newlines);
115+
format!("{escaped}{newlines}")
116+
} else {
117+
message.replace('\n', "%0A")
118+
}
119+
}
120+
121+
#[cfg(test)]
122+
mod tests {
123+
use super::*;
124+
125+
#[test]
126+
fn test_escape_multiline_message_no_newlines() {
127+
assert_eq!(escape_multiline_message("hello world"), "hello world");
128+
}
129+
130+
#[test]
131+
fn test_escape_multiline_message_single_trailing_newline() {
132+
assert_eq!(escape_multiline_message("hello world\n"), "hello world\n");
133+
}
134+
135+
#[test]
136+
fn test_escape_multiline_message_internal_newlines() {
137+
assert_eq!(
138+
escape_multiline_message("line1\nline2\nline3"),
139+
"line1%0Aline2%0Aline3"
140+
);
141+
}
142+
143+
#[test]
144+
fn test_escape_multiline_message_internal_and_trailing_newline() {
145+
assert_eq!(
146+
escape_multiline_message("line1\nline2\nline3\n"),
147+
"line1%0Aline2%0Aline3\n"
148+
);
149+
}
150+
151+
#[test]
152+
fn test_escape_multiline_message_empty_string() {
153+
assert_eq!(escape_multiline_message(""), "");
154+
}
155+
156+
#[test]
157+
fn test_escape_multiline_message_only_newline() {
158+
assert_eq!(escape_multiline_message("\n"), "\n");
159+
}
160+
161+
#[test]
162+
fn test_escape_multiline_message_multiple_trailing_newlines() {
163+
assert_eq!(escape_multiline_message("hello\n\n"), "hello\n\n");
164+
}
165+
}

0 commit comments

Comments
 (0)