@@ -115,8 +115,8 @@ func (repo *Repo) GopkgVersionRoot(version Version) string { | |||||
} | } | ||||
} | } | ||||
var patternOld = regexp.MustCompile(`^/(?:([a-z0-9][-a-z0-9]+)/)?((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2})/([a-zA-Z][-a-zA-Z0-9]*)(?:\.git)?((?:/[a-zA-Z][-a-zA-Z0-9]*)*)$`) | |||||
var patternNew = regexp.MustCompile(`^/(?:([a-zA-Z0-9][-a-zA-Z0-9]+)/)?([a-zA-Z][-.a-zA-Z0-9]*)\.((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2})(?:\.git)?((?:/[a-zA-Z0-9][-.a-zA-Z0-9]*)*)$`) | |||||
var patternOld = regexp.MustCompile(`^/(?:([a-z0-9][-a-z0-9]+)/)?((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2}(-unstable)?)/([a-zA-Z][-a-zA-Z0-9]*)(?:\.git)?((?:/[a-zA-Z][-a-zA-Z0-9]*)*)$`) | |||||
var patternNew = regexp.MustCompile(`^/(?:([a-zA-Z0-9][-a-zA-Z0-9]+)/)?([a-zA-Z][-.a-zA-Z0-9]*)\.((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2}(-unstable)?)(?:\.git)?((?:/[a-zA-Z0-9][-.a-zA-Z0-9]*)*)$`) | |||||
func handler(resp http.ResponseWriter, req *http.Request) { | func handler(resp http.ResponseWriter, req *http.Request) { | ||||
if req.URL.Path == "/health-check" { | if req.URL.Path == "/health-check" { | ||||
@@ -140,6 +140,7 @@ func handler(resp http.ResponseWriter, req *http.Request) { | |||||
sendNotFound(resp, "Unsupported URL pattern; see the documentation at gopkg.in for details.") | sendNotFound(resp, "Unsupported URL pattern; see the documentation at gopkg.in for details.") | ||||
return | return | ||||
} | } | ||||
// "/v2/name" <= "/name.v2" | |||||
m[2], m[3] = m[3], m[2] | m[2], m[3] = m[3], m[2] | ||||
oldFormat = true | oldFormat = true | ||||
} | } | ||||
@@ -153,7 +154,7 @@ func handler(resp http.ResponseWriter, req *http.Request) { | |||||
repo := &Repo{ | repo := &Repo{ | ||||
User: m[1], | User: m[1], | ||||
Name: m[2], | Name: m[2], | ||||
SubPath: m[4], | |||||
SubPath: m[5], | |||||
OldFormat: oldFormat, | OldFormat: oldFormat, | ||||
} | } | ||||
@@ -174,8 +175,14 @@ func handler(resp http.ResponseWriter, req *http.Request) { | |||||
sendNotFound(resp, "GitHub repository not found at https://%s", repo.GitHubRoot()) | sendNotFound(resp, "GitHub repository not found at https://%s", repo.GitHubRoot()) | ||||
return | return | ||||
case ErrNoVersion: | case ErrNoVersion: | ||||
v := repo.MajorVersion.String() | |||||
sendNotFound(resp, `GitHub repository at https://%s has no branch or tag "%s", "%s.N" or "%s.N.M"`, repo.GitHubRoot(), v, v, v) | |||||
major := repo.MajorVersion | |||||
suffix := "" | |||||
if major.Unstable { | |||||
major.Unstable = false | |||||
suffix = unstableSuffix | |||||
} | |||||
v := major.String() | |||||
sendNotFound(resp, `GitHub repository at https://%s has no branch or tag "%s%s", "%s.N%s" or "%s.N.M%s"`, repo.GitHubRoot(), v, suffix, v, suffix, v, suffix) | |||||
return | return | ||||
default: | default: | ||||
resp.WriteHeader(http.StatusBadGateway) | resp.WriteHeader(http.StatusBadGateway) | ||||
@@ -306,7 +313,7 @@ func hackedRefs(repo *Repo) (data []byte, versions []Version, err error) { | |||||
} | } | ||||
// If there were absolutely no versions, and v0 was requested, accept the master as-is. | // If there were absolutely no versions, and v0 was requested, accept the master as-is. | ||||
if len(versions) == 0 && repo.MajorVersion == (Version{0, -1, -1}) { | |||||
if len(versions) == 0 && repo.MajorVersion == (Version{0, -1, -1, false}) { | |||||
return data, nil, nil | return data, nil, nil | ||||
} | } | ||||
@@ -124,6 +124,11 @@ const packageTemplateString = `<!DOCTYPE html> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
{{ if .Repo.MajorVersion.Unstable }} | |||||
<div class="col-sm-12 alert alert-danger"> | |||||
This is an <b><i>unstable</i></b> package and should <i>not</i> be used in released code. | |||||
</div> | |||||
{{ end }} | |||||
<div class="row" > | <div class="row" > | ||||
<div class="col-sm-12" > | <div class="col-sm-12" > | ||||
<a class="btn btn-lg btn-info" href="https://{{.Repo.GitHubRoot}}/tree/{{if .Repo.AllVersions}}{{.FullVersion}}{{else}}master{{end}}{{.Repo.SubPath}}" ><i class="fa fa-github"></i> Source Code</a> | <a class="btn btn-lg btn-info" href="https://{{.Repo.GitHubRoot}}/tree/{{if .Repo.AllVersions}}{{.FullVersion}}{{else}}master{{end}}{{.Repo.SubPath}}" ><i class="fa fa-github"></i> Source Code</a> | ||||
@@ -153,7 +158,7 @@ const packageTemplateString = `<!DOCTYPE html> | |||||
{{ if .LatestVersions }} | {{ if .LatestVersions }} | ||||
{{ range .LatestVersions }} | {{ range .LatestVersions }} | ||||
<div> | <div> | ||||
<a href="//{{gopkgVersionRoot $.Repo .}}{{$.Repo.SubPath}}" {{if eq .Major $.Repo.MajorVersion.Major}}class="current"{{end}} >v{{.Major}}</a> | |||||
<a href="//{{gopkgVersionRoot $.Repo .}}{{$.Repo.SubPath}}" {{if eq .Major $.Repo.MajorVersion.Major}}{{if eq .Unstable $.Repo.MajorVersion.Unstable}}class="current"{{end}}{{end}}>v{{.Major}}{{if .Unstable}}-unstable{{end}}</a> | |||||
→ | → | ||||
<span class="label label-default">{{.}}</span> | <span class="label label-default">{{.}}</span> | ||||
</div> | </div> | ||||
@@ -227,21 +232,33 @@ func renderPackagePage(resp http.ResponseWriter, req *http.Request, repo *Repo) | |||||
Repo: repo, | Repo: repo, | ||||
} | } | ||||
// calculate version mapping | |||||
latestVersionsMap := make(map[int]Version) | |||||
// Calculate the latest version for each major version, both stable and unstable. | |||||
latestVersions := make(map[int]Version) | |||||
latestUnstable := make(map[int]Version) | |||||
for _, v := range repo.AllVersions { | for _, v := range repo.AllVersions { | ||||
v2, exists := latestVersionsMap[v.Major] | |||||
m := latestVersions | |||||
if v.Unstable { | |||||
m = latestUnstable | |||||
} | |||||
v2, exists := m[v.Major] | |||||
if !exists || v2.Less(v) { | if !exists || v2.Less(v) { | ||||
latestVersionsMap[v.Major] = v | |||||
m[v.Major] = v | |||||
} | } | ||||
} | } | ||||
data.FullVersion = latestVersionsMap[repo.MajorVersion.Major] | |||||
data.LatestVersions = make(VersionList, 0, len(latestVersionsMap)) | |||||
for _, v := range latestVersionsMap { | |||||
data.LatestVersions = make(VersionList, 0, len(latestVersions)) | |||||
for _, v := range latestVersions { | |||||
data.LatestVersions = append(data.LatestVersions, v) | data.LatestVersions = append(data.LatestVersions, v) | ||||
} | } | ||||
sort.Sort(sort.Reverse(data.LatestVersions)) | sort.Sort(sort.Reverse(data.LatestVersions)) | ||||
if repo.MajorVersion.Unstable { | |||||
data.FullVersion = latestUnstable[repo.MajorVersion.Major] | |||||
// Prepend post-sorting so it's show first. | |||||
data.LatestVersions = append([]Version{data.FullVersion}, data.LatestVersions...) | |||||
} else { | |||||
data.FullVersion = latestVersions[repo.MajorVersion.Major] | |||||
} | |||||
var dataMutex sync.Mutex | var dataMutex sync.Mutex | ||||
wantResps := 2 | wantResps := 2 | ||||
gotResp := make(chan bool, wantResps) | gotResp := make(chan bool, wantResps) | ||||
@@ -7,20 +7,29 @@ import ( | |||||
// Version represents a version number. | // Version represents a version number. | ||||
// An element that is not present is represented as -1. | // An element that is not present is represented as -1. | ||||
type Version struct { | type Version struct { | ||||
Major, Minor, Patch int | |||||
Major int | |||||
Minor int | |||||
Patch int | |||||
Unstable bool | |||||
} | } | ||||
const unstableSuffix = "-unstable" | |||||
func (v Version) String() string { | func (v Version) String() string { | ||||
if v.Major < 0 { | if v.Major < 0 { | ||||
panic(fmt.Sprintf("cannot stringify invalid version (major is %d)", v.Major)) | panic(fmt.Sprintf("cannot stringify invalid version (major is %d)", v.Major)) | ||||
} | } | ||||
suffix := "" | |||||
if v.Unstable { | |||||
suffix = unstableSuffix | |||||
} | |||||
if v.Minor < 0 { | if v.Minor < 0 { | ||||
return fmt.Sprintf("v%d", v.Major) | |||||
return fmt.Sprintf("v%d%s", v.Major, suffix) | |||||
} | } | ||||
if v.Patch < 0 { | if v.Patch < 0 { | ||||
return fmt.Sprintf("v%d.%d", v.Major, v.Minor) | |||||
return fmt.Sprintf("v%d.%d%s", v.Major, v.Minor, suffix) | |||||
} | } | ||||
return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch) | |||||
return fmt.Sprintf("v%d.%d.%d%s", v.Major, v.Minor, v.Patch, suffix) | |||||
} | } | ||||
// Less returns whether v is less than other. | // Less returns whether v is less than other. | ||||
@@ -31,7 +40,10 @@ func (v Version) Less(other Version) bool { | |||||
if v.Minor != other.Minor { | if v.Minor != other.Minor { | ||||
return v.Minor < other.Minor | return v.Minor < other.Minor | ||||
} | } | ||||
return v.Patch < other.Patch | |||||
if v.Patch != other.Patch { | |||||
return v.Patch < other.Patch | |||||
} | |||||
return v.Unstable && !other.Unstable | |||||
} | } | ||||
// Contains returns whether version v contains version other. | // Contains returns whether version v contains version other. | ||||
@@ -40,7 +52,13 @@ func (v Version) Less(other Version) bool { | |||||
// | // | ||||
// For example, Version{1, 1, -1} contains both Version{1, 1, -1} and Version{1, 1, 2}, | // For example, Version{1, 1, -1} contains both Version{1, 1, -1} and Version{1, 1, 2}, | ||||
// but not Version{1, -1, -1} or Version{1, 2, -1}. | // but not Version{1, -1, -1} or Version{1, 2, -1}. | ||||
// | |||||
// Unstable versions (-unstable) only contain unstable versions, and stable | |||||
// versions only contain stable versions. | |||||
func (v Version) Contains(other Version) bool { | func (v Version) Contains(other Version) bool { | ||||
if v.Unstable != other.Unstable { | |||||
return false | |||||
} | |||||
if v.Patch != -1 { | if v.Patch != -1 { | ||||
return v == other | return v == other | ||||
} | } | ||||
@@ -55,61 +73,65 @@ func (v Version) IsValid() bool { | |||||
} | } | ||||
// InvalidVersion represents a version that can't be parsed. | // InvalidVersion represents a version that can't be parsed. | ||||
var InvalidVersion = Version{-1, -1, -1} | |||||
var InvalidVersion = Version{-1, -1, -1, false} | |||||
func parseVersion(s string) (Version, bool) { | |||||
func parseVersion(s string) (v Version, ok bool) { | |||||
v = InvalidVersion | |||||
if len(s) < 2 { | if len(s) < 2 { | ||||
return InvalidVersion, false | |||||
return | |||||
} | } | ||||
if s[0] != 'v' { | if s[0] != 'v' { | ||||
return InvalidVersion, false | |||||
return | |||||
} | } | ||||
v := Version{-1, -1, -1} | |||||
vout := InvalidVersion | |||||
unstable := false | |||||
i := 1 | i := 1 | ||||
v.Major, i = parseVersionPart(s, i) | |||||
if i < 0 { | |||||
return InvalidVersion, false | |||||
} | |||||
if i == len(s) { | |||||
return v, true | |||||
} | |||||
v.Minor, i = parseVersionPart(s, i) | |||||
if i < 0 { | |||||
return InvalidVersion, false | |||||
} | |||||
if i == len(s) { | |||||
return v, true | |||||
} | |||||
v.Patch, i = parseVersionPart(s, i) | |||||
if i < 0 || i < len(s) { | |||||
return InvalidVersion, false | |||||
for _, vptr := range []*int{&vout.Major, &vout.Minor, &vout.Patch} { | |||||
*vptr, unstable, i = parseVersionPart(s, i) | |||||
if i < 0 { | |||||
return | |||||
} | |||||
if i == len(s) { | |||||
vout.Unstable = unstable | |||||
return vout, true | |||||
} | |||||
} | } | ||||
return v, true | |||||
return | |||||
} | } | ||||
func parseVersionPart(s string, i int) (part int, newi int) { | |||||
dot := i | |||||
for dot < len(s) && s[dot] != '.' { | |||||
dot++ | |||||
} | |||||
if dot == i || dot-i > 1 && s[i] == '0' { | |||||
return -1, -1 | |||||
} | |||||
for i < len(s) { | |||||
if s[i] < '0' || s[i] > '9' { | |||||
return -1, -1 | |||||
func parseVersionPart(s string, i int) (part int, unstable bool, newi int) { | |||||
j := i | |||||
for j < len(s) && s[j] != '.' && s[j] != '-' { | |||||
j++ | |||||
} | |||||
if j == i || j-i > 1 && s[i] == '0' { | |||||
return -1, false, -1 | |||||
} | |||||
c := s[i] | |||||
for { | |||||
if c < '0' || c > '9' { | |||||
return -1, false, -1 | |||||
} | } | ||||
part *= 10 | part *= 10 | ||||
part += int(s[i] - '0') | |||||
part += int(c - '0') | |||||
if part < 0 { | if part < 0 { | ||||
return -1, -1 | |||||
return -1, false, -1 | |||||
} | } | ||||
i++ | i++ | ||||
if i+1 < len(s) && s[i] == '.' { | |||||
return part, i + 1 | |||||
if i == len(s) { | |||||
return part, false, i | |||||
} | |||||
c = s[i] | |||||
if i+1 < len(s) { | |||||
if c == '.' { | |||||
return part, false, i + 1 | |||||
} | |||||
if c == '-' && s[i:] == unstableSuffix { | |||||
return part, true, i + len(unstableSuffix) | |||||
} | |||||
} | } | ||||
} | } | ||||
return part, i | |||||
panic("unreachable") | |||||
} | } | ||||
// VersionList implements sort.Interface | // VersionList implements sort.Interface | ||||
@@ -13,31 +13,35 @@ var _ = Suite(&VersionSuite{}) | |||||
type VersionSuite struct{} | type VersionSuite struct{} | ||||
var versionParseTests = []struct { | var versionParseTests = []struct { | ||||
major, minor, patch int | |||||
s string | |||||
major int | |||||
minor int | |||||
patch int | |||||
dev bool | |||||
s string | |||||
}{ | }{ | ||||
{-1, -1, -1, "v"}, | |||||
{-1, -1, -1, "v-1"}, | |||||
{-1, -1, -1, "v01"}, | |||||
{-1, -1, -1, "v1.01"}, | |||||
{-1, -1, -1, "a1"}, | |||||
{-1, -1, -1, "v1a"}, | |||||
{-1, -1, -1, "v1..2"}, | |||||
{-1, -1, -1, "v1.2.3.4"}, | |||||
{-1, -1, -1, "v1."}, | |||||
{-1, -1, -1, "v1.2."}, | |||||
{-1, -1, -1, "v1.2.3."}, | |||||
{-1, -1, -1, false, "v"}, | |||||
{-1, -1, -1, false, "v-1"}, | |||||
{-1, -1, -1, false, "v-deb"}, | |||||
{-1, -1, -1, false, "v01"}, | |||||
{-1, -1, -1, false, "v1.01"}, | |||||
{-1, -1, -1, false, "a1"}, | |||||
{-1, -1, -1, false, "v1a"}, | |||||
{-1, -1, -1, false, "v1..2"}, | |||||
{-1, -1, -1, false, "v1.2.3.4"}, | |||||
{-1, -1, -1, false, "v1."}, | |||||
{-1, -1, -1, false, "v1.2."}, | |||||
{-1, -1, -1, false, "v1.2.3."}, | |||||
{0, -1, -1, | |||||
"v0"}, | |||||
{1, -1, -1, | |||||
"v1"}, | |||||
{1, 2, -1, | |||||
"v1.2"}, | |||||
{1, 2, 3, | |||||
"v1.2.3"}, | |||||
{12, 34, 56, | |||||
"v12.34.56"}, | |||||
{0, -1, -1, false, "v0"}, | |||||
{0, -1, -1, true, "v0-unstable"}, | |||||
{1, -1, -1, false, "v1"}, | |||||
{1, -1, -1, true, "v1-unstable"}, | |||||
{1, 2, -1, false, "v1.2"}, | |||||
{1, 2, -1, true, "v1.2-unstable"}, | |||||
{1, 2, 3, false, "v1.2.3"}, | |||||
{1, 2, 3, true, "v1.2.3-unstable"}, | |||||
{12, 34, 56, false, "v12.34.56"}, | |||||
{12, 34, 56, true, "v12.34.56-unstable"}, | |||||
} | } | ||||
func (s *VersionSuite) TestParse(c *C) { | func (s *VersionSuite) TestParse(c *C) { | ||||
@@ -48,7 +52,7 @@ func (s *VersionSuite) TestParse(c *C) { | |||||
c.Fatalf("version %q is invalid but parsed as %#v", t.s, got) | c.Fatalf("version %q is invalid but parsed as %#v", t.s, got) | ||||
} | } | ||||
} else { | } else { | ||||
want := Version{t.major, t.minor, t.patch} | |||||
want := Version{t.major, t.minor, t.patch, t.dev} | |||||
if got != want { | if got != want { | ||||
c.Fatalf("version %q must parse as %#v, got %#v", t.s, want, got) | c.Fatalf("version %q must parse as %#v, got %#v", t.s, want, got) | ||||
} | } | ||||
@@ -61,24 +65,29 @@ func (s *VersionSuite) TestParse(c *C) { | |||||
var versionLessTests = []struct { | var versionLessTests = []struct { | ||||
oneMajor, oneMinor, onePatch int | oneMajor, oneMinor, onePatch int | ||||
oneUnstable bool | |||||
twoMajor, twoMinor, twoPatch int | twoMajor, twoMinor, twoPatch int | ||||
less bool | |||||
twoUnstable, less bool | |||||
}{ | }{ | ||||
{0, 0, 0, 0, 0, 0, false}, | |||||
{1, 0, 0, 1, 0, 0, false}, | |||||
{1, 0, 0, 1, 1, 0, true}, | |||||
{1, 0, 0, 2, 0, 0, true}, | |||||
{0, 1, 0, 0, 1, 0, false}, | |||||
{0, 1, 0, 0, 1, 1, true}, | |||||
{0, 0, 0, 0, 2, 0, true}, | |||||
{0, 0, 1, 0, 0, 1, false}, | |||||
{0, 0, 1, 0, 0, 2, true}, | |||||
{0, 0, 0, false, 0, 0, 0, false, false}, | |||||
{1, 0, 0, false, 1, 0, 0, false, false}, | |||||
{1, 0, 0, false, 1, 1, 0, false, true}, | |||||
{1, 0, 0, false, 2, 0, 0, false, true}, | |||||
{0, 1, 0, false, 0, 1, 0, false, false}, | |||||
{0, 1, 0, false, 0, 1, 1, false, true}, | |||||
{0, 0, 0, false, 0, 2, 0, false, true}, | |||||
{0, 0, 1, false, 0, 0, 1, false, false}, | |||||
{0, 0, 1, false, 0, 0, 2, false, true}, | |||||
{0, 0, 0, false, 0, 0, 0, true, false}, | |||||
{0, 0, 0, true, 0, 0, 0, false, true}, | |||||
{0, 0, 1, true, 0, 0, 0, false, false}, | |||||
} | } | ||||
func (s *VersionSuite) TestLess(c *C) { | func (s *VersionSuite) TestLess(c *C) { | ||||
for _, t := range versionLessTests { | for _, t := range versionLessTests { | ||||
one := Version{t.oneMajor, t.oneMinor, t.onePatch} | |||||
two := Version{t.twoMajor, t.twoMinor, t.twoPatch} | |||||
one := Version{t.oneMajor, t.oneMinor, t.onePatch, t.oneUnstable} | |||||
two := Version{t.twoMajor, t.twoMinor, t.twoPatch, t.twoUnstable} | |||||
if one.Less(two) != t.less { | if one.Less(two) != t.less { | ||||
c.Fatalf("version %s < %s returned %v", one, two, !t.less) | c.Fatalf("version %s < %s returned %v", one, two, !t.less) | ||||
} | } | ||||
@@ -87,21 +96,25 @@ func (s *VersionSuite) TestLess(c *C) { | |||||
var versionContainsTests = []struct { | var versionContainsTests = []struct { | ||||
oneMajor, oneMinor, onePatch int | oneMajor, oneMinor, onePatch int | ||||
oneUnstable bool | |||||
twoMajor, twoMinor, twoPatch int | twoMajor, twoMinor, twoPatch int | ||||
contains bool | |||||
twoUnstable, contains bool | |||||
}{ | }{ | ||||
{12, 34, 56, 12, 34, 56, true}, | |||||
{12, 34, 56, 12, 34, 78, false}, | |||||
{12, 34, -1, 12, 34, 56, true}, | |||||
{12, 34, -1, 12, 78, 56, false}, | |||||
{12, -1, -1, 12, 34, 56, true}, | |||||
{12, -1, -1, 78, 34, 56, false}, | |||||
{12, 34, 56, false, 12, 34, 56, false, true}, | |||||
{12, 34, 56, false, 12, 34, 78, false, false}, | |||||
{12, 34, -1, false, 12, 34, 56, false, true}, | |||||
{12, 34, -1, false, 12, 78, 56, false, false}, | |||||
{12, -1, -1, false, 12, 34, 56, false, true}, | |||||
{12, -1, -1, false, 78, 34, 56, false, false}, | |||||
{12, -1, -1, true, 12, -1, -1, false, false}, | |||||
{12, -1, -1, false, 12, -1, -1, true, false}, | |||||
} | } | ||||
func (s *VersionSuite) TestContains(c *C) { | func (s *VersionSuite) TestContains(c *C) { | ||||
for _, t := range versionContainsTests { | for _, t := range versionContainsTests { | ||||
one := Version{t.oneMajor, t.oneMinor, t.onePatch} | |||||
two := Version{t.twoMajor, t.twoMinor, t.twoPatch} | |||||
one := Version{t.oneMajor, t.oneMinor, t.onePatch, t.oneUnstable} | |||||
two := Version{t.twoMajor, t.twoMinor, t.twoPatch, t.twoUnstable} | |||||
if one.Contains(two) != t.contains { | if one.Contains(two) != t.contains { | ||||
c.Fatalf("version %s.Contains(%s) returned %v", one, two, !t.contains) | c.Fatalf("version %s.Contains(%s) returned %v", one, two, !t.contains) | ||||
} | } | ||||
@@ -110,5 +123,5 @@ func (s *VersionSuite) TestContains(c *C) { | |||||
func (s *VersionSuite) TestIsValid(c *C) { | func (s *VersionSuite) TestIsValid(c *C) { | ||||
c.Assert(InvalidVersion.IsValid(), Equals, false) | c.Assert(InvalidVersion.IsValid(), Equals, false) | ||||
c.Assert(Version{0, 0, 0}.IsValid(), Equals, true) | |||||
c.Assert(Version{0, 0, 0, false}.IsValid(), Equals, true) | |||||
} | } |