mirror of
https://github.com/sbrow/thor.git
synced 2026-10-25 10:11:55 -04:00
fix(feed): Fixed a leak.
This commit is contained in:
@@ -111,8 +111,7 @@ generate_sitemap :: proc(site: ^Site) -> string {
|
|||||||
return strings.to_string(sb)
|
return strings.to_string(sb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Leaks
|
format_rfc822 :: proc(iso: string, allocator := context.temp_allocator) -> string {
|
||||||
format_rfc822 :: proc(iso: string) -> string {
|
|
||||||
if len(iso) < 19 {
|
if len(iso) < 19 {
|
||||||
// TODO: should indicate error somehow
|
// TODO: should indicate error somehow
|
||||||
return iso
|
return iso
|
||||||
@@ -134,6 +133,7 @@ format_rfc822 :: proc(iso: string) -> string {
|
|||||||
t,
|
t,
|
||||||
offset / 60,
|
offset / 60,
|
||||||
offset % 60,
|
offset % 60,
|
||||||
|
allocator = allocator,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
#+test
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "core:strings"
|
||||||
|
import "core:testing"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// xml_escape
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_xml_escape_passthrough :: proc(t: ^testing.T) {
|
||||||
|
testing.expect_value(t, xml_escape(""), "")
|
||||||
|
testing.expect_value(t, xml_escape("hello world"), "hello world")
|
||||||
|
// UTF-8 multibyte bytes are not metacharacters and pass through untouched.
|
||||||
|
testing.expect_value(t, xml_escape("héllo — 世界"), "héllo — 世界")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_xml_escape_entities :: proc(t: ^testing.T) {
|
||||||
|
testing.expect_value(t, xml_escape("&"), "&")
|
||||||
|
testing.expect_value(t, xml_escape("<"), "<")
|
||||||
|
testing.expect_value(t, xml_escape(">"), ">")
|
||||||
|
// Adjacent specials, and specials at the very start/end of the string.
|
||||||
|
testing.expect_value(t, xml_escape("&<>"), "&<>")
|
||||||
|
testing.expect_value(t, xml_escape("<a>"), "<a>")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_xml_escape_mixed :: proc(t: ^testing.T) {
|
||||||
|
testing.expect_value(t, xml_escape("a<b>c&d"), "a<b>c&d")
|
||||||
|
testing.expect_value(
|
||||||
|
t,
|
||||||
|
xml_escape("<p>Tom & Jerry</p>"),
|
||||||
|
"<p>Tom & Jerry</p>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// format_rfc822
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_format_rfc822_short_passthrough :: proc(t: ^testing.T) {
|
||||||
|
// Inputs shorter than a full ISO-8601 timestamp are returned unchanged
|
||||||
|
// (and are the input slice itself, so they must not be deleted).
|
||||||
|
testing.expect_value(t, format_rfc822("2024"), "2024")
|
||||||
|
testing.expect_value(t, format_rfc822(""), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_format_rfc822_full_date :: proc(t: ^testing.T) {
|
||||||
|
got := format_rfc822("2024-01-15T10:30:00Z")
|
||||||
|
// Result is temp-allocated (reset by the test runner), so no delete.
|
||||||
|
// 2024-01-15 is a Monday. Assert the stable leading portion; the trailing
|
||||||
|
// offset rendering is intentionally left unpinned here.
|
||||||
|
testing.expect(t, strings.has_prefix(got, "Mon, 15 Jan 2024 10:30:00"), got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// generate_rss
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_generate_rss_structure :: proc(t: ^testing.T) {
|
||||||
|
site: Site
|
||||||
|
site.title = "My & Site"
|
||||||
|
site.description = "Desc <here>"
|
||||||
|
site.base_url = "https://example.com"
|
||||||
|
site.pages = make(#soa[dynamic]Page)
|
||||||
|
defer delete_soa(site.pages)
|
||||||
|
|
||||||
|
// A real content page.
|
||||||
|
append(
|
||||||
|
&site.pages,
|
||||||
|
Page {
|
||||||
|
section = "posts",
|
||||||
|
title = "First <Post>",
|
||||||
|
url = "https://example.com/posts/first/",
|
||||||
|
content = "<p>Hello & welcome</p>",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// The home index page (section == "" && _is_index) must be skipped.
|
||||||
|
append(
|
||||||
|
&site.pages,
|
||||||
|
Page {
|
||||||
|
section = "",
|
||||||
|
_is_index = true,
|
||||||
|
title = "HOME_SHOULD_BE_SKIPPED",
|
||||||
|
url = "https://example.com/",
|
||||||
|
content = "home",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// A second content page.
|
||||||
|
append(
|
||||||
|
&site.pages,
|
||||||
|
Page {
|
||||||
|
section = "posts",
|
||||||
|
title = "Second Post",
|
||||||
|
url = "https://example.com/posts/second/",
|
||||||
|
content = "plain body",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
rss := generate_rss(&site)
|
||||||
|
defer delete(rss)
|
||||||
|
|
||||||
|
// Channel header: site title/description are XML-escaped, link gets a
|
||||||
|
// trailing slash, and the atom self-link points at index.xml.
|
||||||
|
testing.expect(t, strings.contains(rss, "<title>My & Site</title>"), rss)
|
||||||
|
testing.expect(
|
||||||
|
t,
|
||||||
|
strings.contains(rss, "<description>Desc <here></description>"),
|
||||||
|
rss,
|
||||||
|
)
|
||||||
|
testing.expect(t, strings.contains(rss, "<link>https://example.com/</link>"), rss)
|
||||||
|
testing.expect(
|
||||||
|
t,
|
||||||
|
strings.contains(rss, `<atom:link href="https://example.com/index.xml"`),
|
||||||
|
rss,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exactly two <item>s — the home index page is skipped.
|
||||||
|
testing.expect_value(t, strings.count(rss, "<item>"), 2)
|
||||||
|
testing.expect(t, !strings.contains(rss, "HOME_SHOULD_BE_SKIPPED"), rss)
|
||||||
|
|
||||||
|
// First item: title and body are XML-escaped, link is verbatim.
|
||||||
|
testing.expect(t, strings.contains(rss, "<title>First <Post></title>"), rss)
|
||||||
|
testing.expect(
|
||||||
|
t,
|
||||||
|
strings.contains(
|
||||||
|
rss,
|
||||||
|
"<description><p>Hello & welcome</p></description>",
|
||||||
|
),
|
||||||
|
rss,
|
||||||
|
)
|
||||||
|
testing.expect(
|
||||||
|
t,
|
||||||
|
strings.contains(rss, "<link>https://example.com/posts/first/</link>"),
|
||||||
|
rss,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Undated pages get the sentinel pubDate (no date formatting/allocation).
|
||||||
|
testing.expect(
|
||||||
|
t,
|
||||||
|
strings.contains(rss, "<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>"),
|
||||||
|
rss,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Well-formed at the boundaries.
|
||||||
|
testing.expect(t, strings.has_prefix(rss, `<?xml version="1.0"`), rss)
|
||||||
|
testing.expect(t, strings.has_suffix(rss, "</channel>\n</rss>"), rss)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user