123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 | package render_test
import (
"archive/tar"
"compress/gzip"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestTags(t *testing.T) {
e := newEnv(t, "proj", "")
e.seed()
e.render()
e.git(e.work, "tag", "v1.0")
e.git(e.work, "tag", "-a", "-m", "release", "rel/2.0")
e.git(e.work, "push", "-q", "origin", "--tags")
sha := strings.TrimSpace(e.git(e.work, "rev-parse", "v1.0"))
if err := e.site.HandleTag("proj", "v1.0", sha); err != nil {
t.Fatal(err)
}
if err := e.site.HandleTag("proj", "rel/2.0", sha); err != nil {
t.Fatal(err)
}
e.mustExist("archive/proj/proj-v1.0.tar.gz", "archive/proj/proj-rel-2.0.tar.gz", "snapshot/proj/proj@"+sha[:12]+".tar.gz")
// Tarball has the expected prefix.
f, err := os.Open(e.h("archive/proj/proj-v1.0.tar.gz"))
if err != nil {
t.Fatal(err)
}
defer func() { _ = f.Close() }() // read only
zr, err := gzip.NewReader(f)
if err != nil {
t.Fatal(err)
}
tr := tar.NewReader(zr)
for {
hdr, err := tr.Next()
if err != nil {
t.Fatal(err)
}
if hdr.Typeflag == tar.TypeXGlobalHeader {
continue // git archive writes the commit id in a pax header first
}
if !strings.HasPrefix(hdr.Name, "proj-v1.0/") {
t.Errorf("tar prefix: %q", hdr.Name)
}
break
}
// Every page's download menu frames the download list, so writing the
// list is all a tag push needs; the pages stay as they are.
root := e.read(e.page(""))
if err := e.site.WriteDownloads("proj"); err != nil {
t.Fatal(err)
}
mustContain(t, "tag list", e.read("iframes/proj/downloads.html"), `href="/archive/proj/proj-v1.0.tar.gz"`, `href="/archive/proj/proj-rel-2.0.tar.gz"`)
for _, pg := range []string{e.page(""), e.page("src/main.c")} {
mustContain(t, pg, e.read(pg), `src="/iframes/proj/downloads.html"`)
mustNotContain(t, pg, e.read(pg), "proj-v1.0", "/archive/", "/snapshot/")
}
if e.read(e.page("")) != root {
t.Error("writing the tag list rewrote a page")
}
// Deleting a tag removes its archive. Unsafe names are skipped.
if err := e.site.HandleTag("proj", "v1.0", strings.Repeat("0", 40)); err != nil {
t.Fatal(err)
}
e.mustNotExist("archive/proj/proj-v1.0.tar.gz")
if err := e.site.WriteDownloads("proj"); err != nil {
t.Fatal(err)
}
mustNotContain(t, "tag list", e.read("iframes/proj/downloads.html"), "proj-v1.0")
if err := e.site.HandleTag("proj", "../evil", sha); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(filepath.Join(e.root, "htdocs", e.host, "archive", "proj", "proj-..-evil.tar.gz")); err == nil {
t.Error("unsafe tag produced an archive")
}
// A render picks up archives for existing tags and lists them straight
// away (a fresh deploy has no archives yet).
if err := os.Remove(e.h("archive/proj/proj-rel-2.0.tar.gz")); err != nil {
t.Fatal(err)
}
if _, err := e.site.RenderRepo("proj", false); err != nil {
t.Fatal(err)
}
e.mustExist("archive/proj/proj-rel-2.0.tar.gz")
mustContain(t, "tag list after render", e.read("iframes/proj/downloads.html"), `href="/archive/proj/proj-rel-2.0.tar.gz"`)
}
// The branch-tip tarball is named by its commit, so its URL can be cached
// for good. The download list links the current one and names its commit;
// the pages never do, so a push leaves untouched pages alone. A replaced
// tarball stays for archive_retention after the push that replaced it.
func TestTipArchive(t *testing.T) {
e := newEnv(t, "proj", "")
e.seed()
e.render()
tipOf := func() (sha, rel string) {
sha = strings.TrimSpace(e.git(e.work, "rev-parse", "HEAD"))
return sha, "snapshot/proj/proj@" + sha[:12] + ".tar.gz"
}
sha1, tip1 := tipOf()
e.mustExist(tip1)
mustContain(t, "download list", e.read("iframes/proj/downloads.html"), `href="/`+tip1+`"`, ">"+sha1[:12]+"<", ">initial<")
untouched := e.read(e.page("src/main.c"))
mustNotContain(t, "page", untouched, sha1[:12], "/snapshot/")
if gunzip(t, e.h(tip1)) == "" {
t.Error("empty tip tarball")
}
// Made long ago, but replaced only now: it stays.
old := time.Now().Add(-2 * time.Hour)
if err := os.Chtimes(e.h(tip1), old, old); err != nil {
t.Fatal(err)
}
e.write("README.md", "# Hello again\n")
e.commit("second")
e.push(false)
e.render()
_, tip2 := tipOf()
e.mustExist(tip1, tip2)
list := e.read("iframes/proj/downloads.html")
mustContain(t, "download list", list, `href="/`+tip2+`"`, ">second<")
mustNotContain(t, "download list", list, tip1)
if e.read(e.page("src/main.c")) != untouched {
t.Error("a push rewrote a page it did not touch")
}
// Replaced more than archive_retention ago: the next render removes it.
if err := os.Chtimes(e.h(tip1), old, old); err != nil {
t.Fatal(err)
}
e.render()
e.mustNotExist(tip1)
e.mustExist(tip2)
}
|