Compare commits
17 Commits
master
...
release.r5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca2cb382ba | ||
|
|
658a82a13b | ||
|
|
9f89972666 | ||
|
|
125e91a174 | ||
|
|
3854d346f6 | ||
|
|
348ffe7786 | ||
|
|
69e88b5c52 | ||
|
|
006e5e279b | ||
|
|
5af3c75e30 | ||
|
|
a7ae73d4e0 | ||
|
|
6a9e2c7279 | ||
|
|
50e57603ab | ||
|
|
d00421a6b1 | ||
|
|
b69a320781 | ||
|
|
ed54b19716 | ||
|
|
29fdaadbef | ||
|
|
5ce9a8d4fc |
@@ -139,9 +139,8 @@ h1#title {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
#content h2 {
|
#content h2 {
|
||||||
border-top: 1px solid #ddd;
|
border-top: 2px solid #ddd;
|
||||||
background: #E2E7F0;
|
padding: 8px 5px;
|
||||||
padding: 5px;
|
|
||||||
margin: 1.5em 0 0;
|
margin: 1.5em 0 0;
|
||||||
}
|
}
|
||||||
#content .subtitle {
|
#content .subtitle {
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="roadmap.html">Roadmap</a></li>
|
<li><a href="roadmap.html">Roadmap</a></li>
|
||||||
<li><a href="release.html">Release History</a></li>
|
<li><a href="release.html">Release history</a></li>
|
||||||
|
<li><a href="weekly.html">Weekly snapshot history</a></li>
|
||||||
<li><a href="http://godashboard.appspot.com">Build and benchmark status</a></li>
|
<li><a href="http://godashboard.appspot.com">Build and benchmark status</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -45,11 +45,7 @@ Debugger.
|
|||||||
<li>
|
<li>
|
||||||
App Engine support.
|
App Engine support.
|
||||||
<li>
|
<li>
|
||||||
Improved CGO including some mechanism for calling back from C to Go.
|
|
||||||
<li>
|
|
||||||
Improved implementation documentation.
|
Improved implementation documentation.
|
||||||
<li>
|
|
||||||
Faster, allocation-light reflection.
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h4 id="Gc_roadmap">
|
<h4 id="Gc_roadmap">
|
||||||
@@ -91,8 +87,6 @@ Packages roadmap</h4>
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Faster, allocation-light reflection.
|
|
||||||
<li>
|
|
||||||
Faster, RE2-like regular expressions.
|
Faster, RE2-like regular expressions.
|
||||||
<li>
|
<li>
|
||||||
Comprehensive support for international text.
|
Comprehensive support for international text.
|
||||||
@@ -134,4 +128,8 @@ Package manager (goinstall).
|
|||||||
A means of recovering from a panic (recover).
|
A means of recovering from a panic (recover).
|
||||||
<li>
|
<li>
|
||||||
5g: Better floating point support.
|
5g: Better floating point support.
|
||||||
|
<li>
|
||||||
|
Improved CGO including some mechanism for calling back from C to Go.
|
||||||
|
<li>
|
||||||
|
Faster, allocation-light reflection.
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
2944
doc/devel/weekly.html
Normal file
2944
doc/devel/weekly.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -136,7 +136,7 @@ and make sure the <code>go</code> directory does not exist.
|
|||||||
Then check out the repository:</p>
|
Then check out the repository:</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
$ hg clone -r release https://go.googlecode.com/hg/ go
|
$ hg clone -u release https://go.googlecode.com/hg/ go
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<h2 id="install">Install Go</h2>
|
<h2 id="install">Install Go</h2>
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ defaultcc = None
|
|||||||
contributors = {}
|
contributors = {}
|
||||||
missing_codereview = None
|
missing_codereview = None
|
||||||
real_rollback = None
|
real_rollback = None
|
||||||
|
releaseBranch = None
|
||||||
|
|
||||||
#######################################################################
|
#######################################################################
|
||||||
# RE: UNICODE STRING HANDLING
|
# RE: UNICODE STRING HANDLING
|
||||||
@@ -1049,7 +1050,7 @@ def change(ui, repo, *pats, **opts):
|
|||||||
|
|
||||||
if missing_codereview:
|
if missing_codereview:
|
||||||
return missing_codereview
|
return missing_codereview
|
||||||
|
|
||||||
dirty = {}
|
dirty = {}
|
||||||
if len(pats) > 0 and GoodCLName(pats[0]):
|
if len(pats) > 0 and GoodCLName(pats[0]):
|
||||||
name = pats[0]
|
name = pats[0]
|
||||||
@@ -1062,6 +1063,8 @@ def change(ui, repo, *pats, **opts):
|
|||||||
if not cl.local and (opts["stdin"] or not opts["stdout"]):
|
if not cl.local and (opts["stdin"] or not opts["stdout"]):
|
||||||
return "cannot change non-local CL " + name
|
return "cannot change non-local CL " + name
|
||||||
else:
|
else:
|
||||||
|
if repo[None].branch() != "default":
|
||||||
|
return "cannot run hg change outside default branch"
|
||||||
name = "new"
|
name = "new"
|
||||||
cl = CL("new")
|
cl = CL("new")
|
||||||
dirty[cl] = True
|
dirty[cl] = True
|
||||||
@@ -1154,7 +1157,9 @@ def clpatch(ui, repo, clname, **opts):
|
|||||||
Submitting an imported patch will keep the original author's
|
Submitting an imported patch will keep the original author's
|
||||||
name as the Author: line but add your own name to a Committer: line.
|
name as the Author: line but add your own name to a Committer: line.
|
||||||
"""
|
"""
|
||||||
return clpatch_or_undo(ui, repo, clname, opts)
|
if repo[None].branch() != "default":
|
||||||
|
return "cannot run hg clpatch outside default branch"
|
||||||
|
return clpatch_or_undo(ui, repo, clname, opts, mode="clpatch")
|
||||||
|
|
||||||
def undo(ui, repo, clname, **opts):
|
def undo(ui, repo, clname, **opts):
|
||||||
"""undo the effect of a CL
|
"""undo the effect of a CL
|
||||||
@@ -1163,7 +1168,66 @@ def undo(ui, repo, clname, **opts):
|
|||||||
After creating the CL, opens the CL text for editing so that
|
After creating the CL, opens the CL text for editing so that
|
||||||
you can add the reason for the undo to the description.
|
you can add the reason for the undo to the description.
|
||||||
"""
|
"""
|
||||||
return clpatch_or_undo(ui, repo, clname, opts, undo=True)
|
if repo[None].branch() != "default":
|
||||||
|
return "cannot run hg undo outside default branch"
|
||||||
|
return clpatch_or_undo(ui, repo, clname, opts, mode="undo")
|
||||||
|
|
||||||
|
def release_apply(ui, repo, clname, **opts):
|
||||||
|
"""apply a CL to the release branch
|
||||||
|
|
||||||
|
Creates a new CL copying a previously committed change
|
||||||
|
from the main branch to the release branch.
|
||||||
|
The current client must either be clean or already be in
|
||||||
|
the release branch.
|
||||||
|
|
||||||
|
The release branch must be created by starting with a
|
||||||
|
clean client, disabling the code review plugin, and running:
|
||||||
|
|
||||||
|
hg update weekly.YYYY-MM-DD
|
||||||
|
hg branch release-branch.rNN
|
||||||
|
hg commit -m 'create release-branch.rNN'
|
||||||
|
hg push --new-branch
|
||||||
|
|
||||||
|
Then re-enable the code review plugin.
|
||||||
|
|
||||||
|
People can test the release branch by running
|
||||||
|
|
||||||
|
hg update release-branch.rNN
|
||||||
|
|
||||||
|
in a clean client. To return to the normal tree,
|
||||||
|
|
||||||
|
hg update default
|
||||||
|
|
||||||
|
Move changes since the weekly into the release branch
|
||||||
|
using hg release-apply followed by the usual code review
|
||||||
|
process and hg submit.
|
||||||
|
|
||||||
|
When it comes time to tag the release, record the
|
||||||
|
final long-form tag of the release-branch.rNN
|
||||||
|
in the *default* branch's .hgtags file. That is, run
|
||||||
|
|
||||||
|
hg update default
|
||||||
|
|
||||||
|
and then edit .hgtags as you would for a weekly.
|
||||||
|
|
||||||
|
"""
|
||||||
|
c = repo[None]
|
||||||
|
if not releaseBranch:
|
||||||
|
return "no active release branches"
|
||||||
|
if c.branch() != releaseBranch:
|
||||||
|
if c.modified() or c.added() or c.removed():
|
||||||
|
raise util.Abort("uncommitted local changes - cannot switch branches")
|
||||||
|
err = hg.clean(repo, releaseBranch)
|
||||||
|
if err:
|
||||||
|
return err
|
||||||
|
try:
|
||||||
|
err = clpatch_or_undo(ui, repo, clname, opts, mode="backport")
|
||||||
|
if err:
|
||||||
|
raise util.Abort(err)
|
||||||
|
except Exception, e:
|
||||||
|
hg.clean(repo, "default")
|
||||||
|
raise e
|
||||||
|
return None
|
||||||
|
|
||||||
def rev2clname(rev):
|
def rev2clname(rev):
|
||||||
# Extract CL name from revision description.
|
# Extract CL name from revision description.
|
||||||
@@ -1185,15 +1249,24 @@ undoFooter = """
|
|||||||
»»»
|
»»»
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
backportHeader = """[%s] %s
|
||||||
|
|
||||||
|
««« CL %s / %s
|
||||||
|
"""
|
||||||
|
|
||||||
|
backportFooter = """
|
||||||
|
»»»
|
||||||
|
"""
|
||||||
|
|
||||||
# Implementation of clpatch/undo.
|
# Implementation of clpatch/undo.
|
||||||
def clpatch_or_undo(ui, repo, clname, opts, undo=False):
|
def clpatch_or_undo(ui, repo, clname, opts, mode):
|
||||||
if missing_codereview:
|
if missing_codereview:
|
||||||
return missing_codereview
|
return missing_codereview
|
||||||
|
|
||||||
if undo:
|
if mode == "undo" or mode == "backport":
|
||||||
if hgversion < '1.4':
|
if hgversion < '1.4':
|
||||||
# Don't have cmdutil.match (see implementation of sync command).
|
# Don't have cmdutil.match (see implementation of sync command).
|
||||||
return "hg is too old to run hg undo - update to 1.4 or newer"
|
return "hg is too old to run hg %s - update to 1.4 or newer" % mode
|
||||||
|
|
||||||
# Find revision in Mercurial repository.
|
# Find revision in Mercurial repository.
|
||||||
# Assume CL number is 7+ decimal digits.
|
# Assume CL number is 7+ decimal digits.
|
||||||
@@ -1227,8 +1300,19 @@ def clpatch_or_undo(ui, repo, clname, opts, undo=False):
|
|||||||
# Create fresh CL and start with patch that would reverse the change.
|
# Create fresh CL and start with patch that would reverse the change.
|
||||||
vers = short(rev.node())
|
vers = short(rev.node())
|
||||||
cl = CL("new")
|
cl = CL("new")
|
||||||
cl.desc = (undoHeader % (clname, vers)) + rev.description() + undoFooter
|
desc = rev.description()
|
||||||
patch = RunShell(["hg", "diff", "--git", "-r", vers + ":" + short(rev.parents()[0].node())])
|
if mode == "undo":
|
||||||
|
cl.desc = (undoHeader % (clname, vers)) + desc + undoFooter
|
||||||
|
else:
|
||||||
|
cl.desc = (backportHeader % (releaseBranch, line1(desc), clname, vers)) + desc + undoFooter
|
||||||
|
v1 = vers
|
||||||
|
v0 = short(rev.parents()[0].node())
|
||||||
|
if mode == "undo":
|
||||||
|
arg = v1 + ":" + v0
|
||||||
|
else:
|
||||||
|
vers = v0
|
||||||
|
arg = v0 + ":" + v1
|
||||||
|
patch = RunShell(["hg", "diff", "--git", "-r", arg])
|
||||||
|
|
||||||
else: # clpatch
|
else: # clpatch
|
||||||
cl, vers, patch, err = DownloadCL(ui, repo, clname)
|
cl, vers, patch, err = DownloadCL(ui, repo, clname)
|
||||||
@@ -1249,10 +1333,10 @@ def clpatch_or_undo(ui, repo, clname, opts, undo=False):
|
|||||||
if id != vers:
|
if id != vers:
|
||||||
patch, err = portPatch(repo, patch, vers, id)
|
patch, err = portPatch(repo, patch, vers, id)
|
||||||
if err != "":
|
if err != "":
|
||||||
return "codereview issue %s is out of date: %s" % (clname, err)
|
return "codereview issue %s is out of date: %s (%s->%s)" % (clname, err, vers, id)
|
||||||
|
|
||||||
argv = ["hgpatch"]
|
argv = ["hgpatch"]
|
||||||
if opts["no_incoming"]:
|
if opts["no_incoming"] or mode == "backport":
|
||||||
argv += ["--checksync=false"]
|
argv += ["--checksync=false"]
|
||||||
try:
|
try:
|
||||||
cmd = subprocess.Popen(argv, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None, close_fds=sys.platform != "win32")
|
cmd = subprocess.Popen(argv, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None, close_fds=sys.platform != "win32")
|
||||||
@@ -1271,7 +1355,7 @@ def clpatch_or_undo(ui, repo, clname, opts, undo=False):
|
|||||||
if extra:
|
if extra:
|
||||||
ui.warn("warning: these files were listed in the patch but not changed:\n\t" + "\n\t".join(extra) + "\n")
|
ui.warn("warning: these files were listed in the patch but not changed:\n\t" + "\n\t".join(extra) + "\n")
|
||||||
cl.Flush(ui, repo)
|
cl.Flush(ui, repo)
|
||||||
if undo:
|
if mode == "undo":
|
||||||
err = EditCL(ui, repo, cl)
|
err = EditCL(ui, repo, cl)
|
||||||
if err != "":
|
if err != "":
|
||||||
return "CL created, but error editing: " + err
|
return "CL created, but error editing: " + err
|
||||||
@@ -1506,7 +1590,7 @@ def reposetup(ui, repo):
|
|||||||
|
|
||||||
def CheckContributor(ui, repo, user=None):
|
def CheckContributor(ui, repo, user=None):
|
||||||
set_status("checking CONTRIBUTORS file")
|
set_status("checking CONTRIBUTORS file")
|
||||||
_, userline = FindContributor(ui, repo, user, warn=False)
|
user, userline = FindContributor(ui, repo, user, warn=False)
|
||||||
if not userline:
|
if not userline:
|
||||||
raise util.Abort("cannot find %s in CONTRIBUTORS" % (user,))
|
raise util.Abort("cannot find %s in CONTRIBUTORS" % (user,))
|
||||||
return userline
|
return userline
|
||||||
@@ -1524,7 +1608,7 @@ def FindContributor(ui, repo, user=None, warn=True):
|
|||||||
if user not in contributors:
|
if user not in contributors:
|
||||||
if warn:
|
if warn:
|
||||||
ui.warn("warning: cannot find %s in CONTRIBUTORS\n" % (user,))
|
ui.warn("warning: cannot find %s in CONTRIBUTORS\n" % (user,))
|
||||||
return None, None
|
return user, None
|
||||||
|
|
||||||
user, email = contributors[user]
|
user, email = contributors[user]
|
||||||
return email, "%s <%s>" % (user, email)
|
return email, "%s <%s>" % (user, email)
|
||||||
@@ -1650,6 +1734,14 @@ def submit(ui, repo, *pats, **opts):
|
|||||||
if not cl.copied_from:
|
if not cl.copied_from:
|
||||||
EditDesc(cl.name, closed=True, private=cl.private)
|
EditDesc(cl.name, closed=True, private=cl.private)
|
||||||
cl.Delete(ui, repo)
|
cl.Delete(ui, repo)
|
||||||
|
|
||||||
|
c = repo[None]
|
||||||
|
if c.branch() == releaseBranch and not c.modified() and not c.added() and not c.removed():
|
||||||
|
ui.write("switching from %s to default branch.\n" % releaseBranch)
|
||||||
|
err = hg.clean(repo, "default")
|
||||||
|
if err:
|
||||||
|
return err
|
||||||
|
return None
|
||||||
|
|
||||||
def sync(ui, repo, **opts):
|
def sync(ui, repo, **opts):
|
||||||
"""synchronize with remote repository
|
"""synchronize with remote repository
|
||||||
@@ -1822,6 +1914,15 @@ cmdtable = {
|
|||||||
] + commands.walkopts,
|
] + commands.walkopts,
|
||||||
"[-r reviewer] [--cc cc] [change# | file ...]"
|
"[-r reviewer] [--cc cc] [change# | file ...]"
|
||||||
),
|
),
|
||||||
|
"^release-apply": (
|
||||||
|
release_apply,
|
||||||
|
[
|
||||||
|
('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
|
||||||
|
('', 'no_incoming', None, 'disable check for incoming changes'),
|
||||||
|
],
|
||||||
|
"change#"
|
||||||
|
),
|
||||||
|
# TODO: release-start, release-tag, weekly-tag
|
||||||
"^submit": (
|
"^submit": (
|
||||||
submit,
|
submit,
|
||||||
review_opts + [
|
review_opts + [
|
||||||
@@ -2263,6 +2364,19 @@ def RietveldSetup(ui, repo):
|
|||||||
upload_options.email = "test@example.com"
|
upload_options.email = "test@example.com"
|
||||||
|
|
||||||
rpc = None
|
rpc = None
|
||||||
|
|
||||||
|
global releaseBranch
|
||||||
|
tags = repo.branchtags().keys()
|
||||||
|
if 'release-branch.r100' in tags:
|
||||||
|
# NOTE(rsc): This tags.sort is going to get the wrong
|
||||||
|
# answer when comparing release-branch.r99 with
|
||||||
|
# release-branch.r100. If we do ten releases a year
|
||||||
|
# that gives us 4 years before we have to worry about this.
|
||||||
|
raise util.Abort('tags.sort needs to be fixed for release-branch.r100')
|
||||||
|
tags.sort()
|
||||||
|
for t in tags:
|
||||||
|
if t.startswith('release-branch.'):
|
||||||
|
releaseBranch = t
|
||||||
|
|
||||||
#######################################################################
|
#######################################################################
|
||||||
# http://codereview.appspot.com/static/upload.py, heavily edited.
|
# http://codereview.appspot.com/static/upload.py, heavily edited.
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ _go_.$O: $(GOFILES) $(PREREQ)
|
|||||||
install: $(TARGDIR)/$(TARG)
|
install: $(TARGDIR)/$(TARG)
|
||||||
|
|
||||||
$(TARGDIR)/$(TARG): $(TARG)
|
$(TARGDIR)/$(TARG): $(TARG)
|
||||||
cp -f $(TARG) $(TARGDIR)
|
mkdir -p $(TARGDIR) && cp -f $(TARG) $(TARGDIR)
|
||||||
|
|
||||||
CLEANFILES+=$(TARG) _test _testmain.go
|
CLEANFILES+=$(TARG) _test _testmain.go
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ void
|
|||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
char *p;
|
char *p;
|
||||||
int nout, nproc, i, c;
|
int c;
|
||||||
|
|
||||||
thechar = '5';
|
thechar = '5';
|
||||||
thestring = "arm";
|
thestring = "arm";
|
||||||
@@ -94,46 +94,10 @@ main(int argc, char *argv[])
|
|||||||
print("usage: %ca [-options] file.s\n", thechar);
|
print("usage: %ca [-options] file.s\n", thechar);
|
||||||
errorexit();
|
errorexit();
|
||||||
}
|
}
|
||||||
if(argc > 1 && systemtype(Windows)){
|
if(argc > 1){
|
||||||
print("can't assemble multiple files on windows\n");
|
print("can't assemble multiple files\n");
|
||||||
errorexit();
|
errorexit();
|
||||||
}
|
}
|
||||||
if(argc > 1 && !systemtype(Windows)) {
|
|
||||||
nproc = 1;
|
|
||||||
if(p = getenv("NPROC"))
|
|
||||||
nproc = atol(p); /* */
|
|
||||||
c = 0;
|
|
||||||
nout = 0;
|
|
||||||
for(;;) {
|
|
||||||
Waitmsg *w;
|
|
||||||
|
|
||||||
while(nout < nproc && argc > 0) {
|
|
||||||
i = fork();
|
|
||||||
if(i < 0) {
|
|
||||||
fprint(2, "fork: %r\n");
|
|
||||||
errorexit();
|
|
||||||
}
|
|
||||||
if(i == 0) {
|
|
||||||
print("%s:\n", *argv);
|
|
||||||
if(assemble(*argv))
|
|
||||||
errorexit();
|
|
||||||
exits(0);
|
|
||||||
}
|
|
||||||
nout++;
|
|
||||||
argc--;
|
|
||||||
argv++;
|
|
||||||
}
|
|
||||||
w = wait();
|
|
||||||
if(w == nil) {
|
|
||||||
if(c)
|
|
||||||
errorexit();
|
|
||||||
exits(0);
|
|
||||||
}
|
|
||||||
if(w->msg[0])
|
|
||||||
c++;
|
|
||||||
nout--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(assemble(argv[0]))
|
if(assemble(argv[0]))
|
||||||
errorexit();
|
errorexit();
|
||||||
exits(0);
|
exits(0);
|
||||||
@@ -142,7 +106,7 @@ main(int argc, char *argv[])
|
|||||||
int
|
int
|
||||||
assemble(char *file)
|
assemble(char *file)
|
||||||
{
|
{
|
||||||
char *ofile, incfile[20], *p;
|
char *ofile, *p;
|
||||||
int i, of;
|
int i, of;
|
||||||
|
|
||||||
ofile = alloc(strlen(file)+3); // +3 for .x\0 (x=thechar)
|
ofile = alloc(strlen(file)+3); // +3 for .x\0 (x=thechar)
|
||||||
@@ -167,15 +131,6 @@ assemble(char *file)
|
|||||||
} else
|
} else
|
||||||
outfile = "/dev/null";
|
outfile = "/dev/null";
|
||||||
}
|
}
|
||||||
p = getenv("INCLUDE");
|
|
||||||
if(p) {
|
|
||||||
setinclude(p);
|
|
||||||
} else {
|
|
||||||
if(systemtype(Plan9)) {
|
|
||||||
sprint(incfile,"/%s/include", thestring);
|
|
||||||
setinclude(strdup(incfile));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
of = create(outfile, OWRITE, 0664);
|
of = create(outfile, OWRITE, 0664);
|
||||||
if(of < 0) {
|
if(of < 0) {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ void
|
|||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
char *p;
|
char *p;
|
||||||
int nout, nproc, i, c;
|
int c;
|
||||||
|
|
||||||
thechar = '6';
|
thechar = '6';
|
||||||
thestring = "amd64";
|
thestring = "amd64";
|
||||||
@@ -96,46 +96,10 @@ main(int argc, char *argv[])
|
|||||||
print("usage: %ca [-options] file.s\n", thechar);
|
print("usage: %ca [-options] file.s\n", thechar);
|
||||||
errorexit();
|
errorexit();
|
||||||
}
|
}
|
||||||
if(argc > 1 && systemtype(Windows)){
|
if(argc > 1){
|
||||||
print("can't assemble multiple files on windows\n");
|
print("can't assemble multiple files\n");
|
||||||
errorexit();
|
errorexit();
|
||||||
}
|
}
|
||||||
if(argc > 1 && !systemtype(Windows)) {
|
|
||||||
nproc = 1;
|
|
||||||
if(p = getenv("NPROC"))
|
|
||||||
nproc = atol(p); /* */
|
|
||||||
c = 0;
|
|
||||||
nout = 0;
|
|
||||||
for(;;) {
|
|
||||||
Waitmsg *w;
|
|
||||||
|
|
||||||
while(nout < nproc && argc > 0) {
|
|
||||||
i = fork();
|
|
||||||
if(i < 0) {
|
|
||||||
fprint(2, "fork: %r\n");
|
|
||||||
errorexit();
|
|
||||||
}
|
|
||||||
if(i == 0) {
|
|
||||||
print("%s:\n", *argv);
|
|
||||||
if(assemble(*argv))
|
|
||||||
errorexit();
|
|
||||||
exits(0);
|
|
||||||
}
|
|
||||||
nout++;
|
|
||||||
argc--;
|
|
||||||
argv++;
|
|
||||||
}
|
|
||||||
w = wait();
|
|
||||||
if(w == nil) {
|
|
||||||
if(c)
|
|
||||||
errorexit();
|
|
||||||
exits(0);
|
|
||||||
}
|
|
||||||
if(w->msg[0])
|
|
||||||
c++;
|
|
||||||
nout--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(assemble(argv[0]))
|
if(assemble(argv[0]))
|
||||||
errorexit();
|
errorexit();
|
||||||
exits(0);
|
exits(0);
|
||||||
@@ -144,7 +108,7 @@ main(int argc, char *argv[])
|
|||||||
int
|
int
|
||||||
assemble(char *file)
|
assemble(char *file)
|
||||||
{
|
{
|
||||||
char *ofile, incfile[20], *p;
|
char *ofile, *p;
|
||||||
int i, of;
|
int i, of;
|
||||||
|
|
||||||
ofile = alloc(strlen(file)+3); // +3 for .x\0 (x=thechar)
|
ofile = alloc(strlen(file)+3); // +3 for .x\0 (x=thechar)
|
||||||
@@ -169,15 +133,6 @@ assemble(char *file)
|
|||||||
} else
|
} else
|
||||||
outfile = "/dev/null";
|
outfile = "/dev/null";
|
||||||
}
|
}
|
||||||
p = getenv("INCLUDE");
|
|
||||||
if(p) {
|
|
||||||
setinclude(p);
|
|
||||||
} else {
|
|
||||||
if(systemtype(Plan9)) {
|
|
||||||
sprint(incfile,"/%s/include", thestring);
|
|
||||||
setinclude(strdup(incfile));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
of = create(outfile, OWRITE, 0664);
|
of = create(outfile, OWRITE, 0664);
|
||||||
if(of < 0) {
|
if(of < 0) {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ void
|
|||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
char *p;
|
char *p;
|
||||||
int nout, nproc, i, c;
|
int c;
|
||||||
|
|
||||||
thechar = '8';
|
thechar = '8';
|
||||||
thestring = "386";
|
thestring = "386";
|
||||||
@@ -96,46 +96,10 @@ main(int argc, char *argv[])
|
|||||||
print("usage: %ca [-options] file.s\n", thechar);
|
print("usage: %ca [-options] file.s\n", thechar);
|
||||||
errorexit();
|
errorexit();
|
||||||
}
|
}
|
||||||
if(argc > 1 && systemtype(Windows)){
|
if(argc > 1){
|
||||||
print("can't assemble multiple files on windows\n");
|
print("can't assemble multiple files\n");
|
||||||
errorexit();
|
errorexit();
|
||||||
}
|
}
|
||||||
if(argc > 1 && !systemtype(Windows)) {
|
|
||||||
nproc = 1;
|
|
||||||
if(p = getenv("NPROC"))
|
|
||||||
nproc = atol(p); /* */
|
|
||||||
c = 0;
|
|
||||||
nout = 0;
|
|
||||||
for(;;) {
|
|
||||||
Waitmsg *w;
|
|
||||||
|
|
||||||
while(nout < nproc && argc > 0) {
|
|
||||||
i = fork();
|
|
||||||
if(i < 0) {
|
|
||||||
fprint(2, "fork: %r\n");
|
|
||||||
errorexit();
|
|
||||||
}
|
|
||||||
if(i == 0) {
|
|
||||||
print("%s:\n", *argv);
|
|
||||||
if(assemble(*argv))
|
|
||||||
errorexit();
|
|
||||||
exits(0);
|
|
||||||
}
|
|
||||||
nout++;
|
|
||||||
argc--;
|
|
||||||
argv++;
|
|
||||||
}
|
|
||||||
w = wait();
|
|
||||||
if(w == nil) {
|
|
||||||
if(c)
|
|
||||||
errorexit();
|
|
||||||
exits(0);
|
|
||||||
}
|
|
||||||
if(w->msg[0])
|
|
||||||
c++;
|
|
||||||
nout--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(assemble(argv[0]))
|
if(assemble(argv[0]))
|
||||||
errorexit();
|
errorexit();
|
||||||
exits(0);
|
exits(0);
|
||||||
@@ -144,7 +108,7 @@ main(int argc, char *argv[])
|
|||||||
int
|
int
|
||||||
assemble(char *file)
|
assemble(char *file)
|
||||||
{
|
{
|
||||||
char *ofile, incfile[20], *p;
|
char *ofile, *p;
|
||||||
int i, of;
|
int i, of;
|
||||||
|
|
||||||
ofile = alloc(strlen(file)+3); // +3 for .x\0 (x=thechar)
|
ofile = alloc(strlen(file)+3); // +3 for .x\0 (x=thechar)
|
||||||
@@ -169,15 +133,6 @@ assemble(char *file)
|
|||||||
} else
|
} else
|
||||||
outfile = "/dev/null";
|
outfile = "/dev/null";
|
||||||
}
|
}
|
||||||
p = getenv("INCLUDE");
|
|
||||||
if(p) {
|
|
||||||
setinclude(p);
|
|
||||||
} else {
|
|
||||||
if(systemtype(Plan9)) {
|
|
||||||
sprint(incfile,"/%s/include", thestring);
|
|
||||||
setinclude(strdup(incfile));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
of = create(outfile, OWRITE, 0664);
|
of = create(outfile, OWRITE, 0664);
|
||||||
if(of < 0) {
|
if(of < 0) {
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ void
|
|||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
char **defs, *p;
|
char **defs, *p;
|
||||||
int nproc, nout, i, c, ndef;
|
int c, ndef;
|
||||||
|
|
||||||
ensuresymb(NSYMB);
|
ensuresymb(NSYMB);
|
||||||
memset(debug, 0, sizeof(debug));
|
memset(debug, 0, sizeof(debug));
|
||||||
@@ -142,51 +142,10 @@ main(int argc, char *argv[])
|
|||||||
print("usage: %cc [-options] files\n", thechar);
|
print("usage: %cc [-options] files\n", thechar);
|
||||||
errorexit();
|
errorexit();
|
||||||
}
|
}
|
||||||
if(argc > 1 && systemtype(Windows)){
|
if(argc > 1){
|
||||||
print("can't compile multiple files on windows\n");
|
print("can't compile multiple files\n");
|
||||||
errorexit();
|
errorexit();
|
||||||
}
|
}
|
||||||
if(argc > 1 && !systemtype(Windows)) {
|
|
||||||
nproc = 1;
|
|
||||||
/*
|
|
||||||
* if we're writing acid to standard output, don't compile
|
|
||||||
* concurrently, to avoid interleaving output.
|
|
||||||
*/
|
|
||||||
if(((!debug['a'] && !debug['q'] && !debug['Q']) || debug['n']) &&
|
|
||||||
(p = getenv("NPROC")) != nil)
|
|
||||||
nproc = atol(p); /* */
|
|
||||||
c = 0;
|
|
||||||
nout = 0;
|
|
||||||
for(;;) {
|
|
||||||
Waitmsg *w;
|
|
||||||
|
|
||||||
while(nout < nproc && argc > 0) {
|
|
||||||
i = fork();
|
|
||||||
if(i < 0) {
|
|
||||||
print("cannot create a process\n");
|
|
||||||
errorexit();
|
|
||||||
}
|
|
||||||
if(i == 0) {
|
|
||||||
fprint(2, "%s:\n", *argv);
|
|
||||||
if (compile(*argv, defs, ndef))
|
|
||||||
errorexit();
|
|
||||||
exits(0);
|
|
||||||
}
|
|
||||||
nout++;
|
|
||||||
argc--;
|
|
||||||
argv++;
|
|
||||||
}
|
|
||||||
w = wait();
|
|
||||||
if(w == nil) {
|
|
||||||
if(c)
|
|
||||||
errorexit();
|
|
||||||
exits(0);
|
|
||||||
}
|
|
||||||
if(w->msg[0])
|
|
||||||
c++;
|
|
||||||
nout--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(argc == 0)
|
if(argc == 0)
|
||||||
c = compile("stdin", defs, ndef);
|
c = compile("stdin", defs, ndef);
|
||||||
@@ -201,7 +160,7 @@ main(int argc, char *argv[])
|
|||||||
int
|
int
|
||||||
compile(char *file, char **defs, int ndef)
|
compile(char *file, char **defs, int ndef)
|
||||||
{
|
{
|
||||||
char *ofile, incfile[20];
|
char *ofile;
|
||||||
char *p, **av, opt[256];
|
char *p, **av, opt[256];
|
||||||
int i, c, fd[2];
|
int i, c, fd[2];
|
||||||
static int first = 1;
|
static int first = 1;
|
||||||
@@ -236,15 +195,6 @@ compile(char *file, char **defs, int ndef)
|
|||||||
outfile = "/dev/null";
|
outfile = "/dev/null";
|
||||||
}
|
}
|
||||||
|
|
||||||
if(p = getenv("INCLUDE")) {
|
|
||||||
setinclude(p);
|
|
||||||
} else {
|
|
||||||
if(systemtype(Plan9)) {
|
|
||||||
sprint(incfile, "/%s/include", thestring);
|
|
||||||
setinclude(strdup(incfile));
|
|
||||||
setinclude("/sys/include");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (first)
|
if (first)
|
||||||
Binit(&diagbuf, 1, OWRITE);
|
Binit(&diagbuf, 1, OWRITE);
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -2880,17 +2880,18 @@ sub FetchSymbols {
|
|||||||
my @toask = @pcs;
|
my @toask = @pcs;
|
||||||
while (@toask > 0) {
|
while (@toask > 0) {
|
||||||
my $n = @toask;
|
my $n = @toask;
|
||||||
if ($n > 49) { $n = 49; }
|
# NOTE(rsc): Limiting the number of PCs requested per round
|
||||||
|
# used to be necessary, but I think it was a bug in
|
||||||
|
# debug/pprof/symbol's implementation. Leaving here
|
||||||
|
# in case I am wrong.
|
||||||
|
# if ($n > 49) { $n = 49; }
|
||||||
my @thisround = @toask[0..$n];
|
my @thisround = @toask[0..$n];
|
||||||
my $t = @toask;
|
|
||||||
print STDERR "$n $t\n";
|
|
||||||
@toask = @toask[($n+1)..(@toask-1)];
|
@toask = @toask[($n+1)..(@toask-1)];
|
||||||
my $post_data = join("+", sort((map {"0x" . "$_"} @thisround)));
|
my $post_data = join("+", sort((map {"0x" . "$_"} @thisround)));
|
||||||
open(POSTFILE, ">$main::tmpfile_sym");
|
open(POSTFILE, ">$main::tmpfile_sym");
|
||||||
print POSTFILE $post_data;
|
print POSTFILE $post_data;
|
||||||
close(POSTFILE);
|
close(POSTFILE);
|
||||||
|
|
||||||
print STDERR "SYMBL!\n";
|
|
||||||
my $url = SymbolPageURL();
|
my $url = SymbolPageURL();
|
||||||
$url = ResolveRedirectionForCurl($url);
|
$url = ResolveRedirectionForCurl($url);
|
||||||
my $command_line = "$CURL -sd '\@$main::tmpfile_sym' '$url'";
|
my $command_line = "$CURL -sd '\@$main::tmpfile_sym' '$url'";
|
||||||
|
|||||||
@@ -143,10 +143,18 @@ func (d *compressor) fillWindow(index int) (int, os.Error) {
|
|||||||
d.blockStart = math.MaxInt32
|
d.blockStart = math.MaxInt32
|
||||||
}
|
}
|
||||||
for i, h := range d.hashHead {
|
for i, h := range d.hashHead {
|
||||||
d.hashHead[i] = max(h-wSize, -1)
|
v := h - wSize
|
||||||
|
if v < -1 {
|
||||||
|
v = -1
|
||||||
|
}
|
||||||
|
d.hashHead[i] = v
|
||||||
}
|
}
|
||||||
for i, h := range d.hashPrev {
|
for i, h := range d.hashPrev {
|
||||||
d.hashPrev[i] = max(h-wSize, -1)
|
v := -h - wSize
|
||||||
|
if v < -1 {
|
||||||
|
v = -1
|
||||||
|
}
|
||||||
|
d.hashPrev[i] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count, err := d.r.Read(d.window[d.windowEnd:])
|
count, err := d.r.Read(d.window[d.windowEnd:])
|
||||||
@@ -177,10 +185,18 @@ func (d *compressor) writeBlock(tokens []token, index int, eof bool) os.Error {
|
|||||||
// Try to find a match starting at index whose length is greater than prevSize.
|
// Try to find a match starting at index whose length is greater than prevSize.
|
||||||
// We only look at chainCount possibilities before giving up.
|
// We only look at chainCount possibilities before giving up.
|
||||||
func (d *compressor) findMatch(pos int, prevHead int, prevLength int, lookahead int) (length, offset int, ok bool) {
|
func (d *compressor) findMatch(pos int, prevHead int, prevLength int, lookahead int) (length, offset int, ok bool) {
|
||||||
win := d.window[0 : pos+min(maxMatchLength, lookahead)]
|
minMatchLook := maxMatchLength
|
||||||
|
if lookahead < minMatchLook {
|
||||||
|
minMatchLook = lookahead
|
||||||
|
}
|
||||||
|
|
||||||
|
win := d.window[0 : pos+minMatchLook]
|
||||||
|
|
||||||
// We quit when we get a match that's at least nice long
|
// We quit when we get a match that's at least nice long
|
||||||
nice := min(d.niceMatch, len(win)-pos)
|
nice := len(win) - pos
|
||||||
|
if d.niceMatch < nice {
|
||||||
|
nice = d.niceMatch
|
||||||
|
}
|
||||||
|
|
||||||
// If we've got a match that's good enough, only look in 1/4 the chain.
|
// If we've got a match that's good enough, only look in 1/4 the chain.
|
||||||
tries := d.maxChainLength
|
tries := d.maxChainLength
|
||||||
@@ -344,9 +360,12 @@ Loop:
|
|||||||
}
|
}
|
||||||
prevLength := length
|
prevLength := length
|
||||||
prevOffset := offset
|
prevOffset := offset
|
||||||
minIndex := max(index-maxOffset, 0)
|
|
||||||
length = minMatchLength - 1
|
length = minMatchLength - 1
|
||||||
offset = 0
|
offset = 0
|
||||||
|
minIndex := index - maxOffset
|
||||||
|
if minIndex < 0 {
|
||||||
|
minIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
if chainHead >= minIndex &&
|
if chainHead >= minIndex &&
|
||||||
(isFastDeflate && lookahead > minMatchLength-1 ||
|
(isFastDeflate && lookahead > minMatchLength-1 ||
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ package pprof
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"http"
|
"http"
|
||||||
"os"
|
"os"
|
||||||
@@ -88,10 +89,14 @@ func Profile(w http.ResponseWriter, r *http.Request) {
|
|||||||
func Symbol(w http.ResponseWriter, r *http.Request) {
|
func Symbol(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
|
||||||
|
// We have to read the whole POST body before
|
||||||
|
// writing any output. Buffer the output here.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
// We don't know how many symbols we have, but we
|
// We don't know how many symbols we have, but we
|
||||||
// do have symbol information. Pprof only cares whether
|
// do have symbol information. Pprof only cares whether
|
||||||
// this number is 0 (no symbols available) or > 0.
|
// this number is 0 (no symbols available) or > 0.
|
||||||
fmt.Fprintf(w, "num_symbols: 1\n")
|
fmt.Fprintf(&buf, "num_symbols: 1\n")
|
||||||
|
|
||||||
var b *bufio.Reader
|
var b *bufio.Reader
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
@@ -109,14 +114,19 @@ func Symbol(w http.ResponseWriter, r *http.Request) {
|
|||||||
if pc != 0 {
|
if pc != 0 {
|
||||||
f := runtime.FuncForPC(uintptr(pc))
|
f := runtime.FuncForPC(uintptr(pc))
|
||||||
if f != nil {
|
if f != nil {
|
||||||
fmt.Fprintf(w, "%#x %s\n", pc, f.Name())
|
fmt.Fprintf(&buf, "%#x %s\n", pc, f.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until here to check for err; the last
|
// Wait until here to check for err; the last
|
||||||
// symbol will have an err because it doesn't end in +.
|
// symbol will have an err because it doesn't end in +.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err != os.EOF {
|
||||||
|
fmt.Fprintf(&buf, "reading request: %v\n", err)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Write(buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -439,9 +439,29 @@ type body struct {
|
|||||||
hdr interface{} // non-nil (Response or Request) value means read trailer
|
hdr interface{} // non-nil (Response or Request) value means read trailer
|
||||||
r *bufio.Reader // underlying wire-format reader for the trailer
|
r *bufio.Reader // underlying wire-format reader for the trailer
|
||||||
closing bool // is the connection to be closed after reading body?
|
closing bool // is the connection to be closed after reading body?
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrBodyReadAfterClose is returned when reading a Request Body after
|
||||||
|
// the body has been closed. This typically happens when the body is
|
||||||
|
// read after an HTTP Handler calls WriteHeader or Write on its
|
||||||
|
// ResponseWriter.
|
||||||
|
var ErrBodyReadAfterClose = os.NewError("http: invalid Read on closed request Body")
|
||||||
|
|
||||||
|
func (b *body) Read(p []byte) (n int, err os.Error) {
|
||||||
|
if b.closed {
|
||||||
|
return 0, ErrBodyReadAfterClose
|
||||||
|
}
|
||||||
|
return b.Reader.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *body) Close() os.Error {
|
func (b *body) Close() os.Error {
|
||||||
|
if b.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
b.closed = true
|
||||||
|
}()
|
||||||
if b.hdr == nil && b.closing {
|
if b.hdr == nil && b.closing {
|
||||||
// no trailer and closing the connection next.
|
// no trailer and closing the connection next.
|
||||||
// no point in reading to EOF.
|
// no point in reading to EOF.
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ func (p *RGBA) Set(x, y int, c Color) {
|
|||||||
p.Pix[y*p.Stride+x] = toRGBAColor(c).(RGBAColor)
|
p.Pix[y*p.Stride+x] = toRGBAColor(c).(RGBAColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *RGBA) SetRGBA(x, y int, c RGBAColor) {
|
||||||
|
if !p.Rect.Contains(Point{x, y}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Pix[y*p.Stride+x] = c
|
||||||
|
}
|
||||||
|
|
||||||
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
||||||
func (p *RGBA) Opaque() bool {
|
func (p *RGBA) Opaque() bool {
|
||||||
if p.Rect.Empty() {
|
if p.Rect.Empty() {
|
||||||
@@ -103,6 +110,13 @@ func (p *RGBA64) Set(x, y int, c Color) {
|
|||||||
p.Pix[y*p.Stride+x] = toRGBA64Color(c).(RGBA64Color)
|
p.Pix[y*p.Stride+x] = toRGBA64Color(c).(RGBA64Color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *RGBA64) SetRGBA64(x, y int, c RGBA64Color) {
|
||||||
|
if !p.Rect.Contains(Point{x, y}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Pix[y*p.Stride+x] = c
|
||||||
|
}
|
||||||
|
|
||||||
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
||||||
func (p *RGBA64) Opaque() bool {
|
func (p *RGBA64) Opaque() bool {
|
||||||
if p.Rect.Empty() {
|
if p.Rect.Empty() {
|
||||||
@@ -155,6 +169,13 @@ func (p *NRGBA) Set(x, y int, c Color) {
|
|||||||
p.Pix[y*p.Stride+x] = toNRGBAColor(c).(NRGBAColor)
|
p.Pix[y*p.Stride+x] = toNRGBAColor(c).(NRGBAColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *NRGBA) SetNRGBA(x, y int, c NRGBAColor) {
|
||||||
|
if !p.Rect.Contains(Point{x, y}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Pix[y*p.Stride+x] = c
|
||||||
|
}
|
||||||
|
|
||||||
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
||||||
func (p *NRGBA) Opaque() bool {
|
func (p *NRGBA) Opaque() bool {
|
||||||
if p.Rect.Empty() {
|
if p.Rect.Empty() {
|
||||||
@@ -207,6 +228,13 @@ func (p *NRGBA64) Set(x, y int, c Color) {
|
|||||||
p.Pix[y*p.Stride+x] = toNRGBA64Color(c).(NRGBA64Color)
|
p.Pix[y*p.Stride+x] = toNRGBA64Color(c).(NRGBA64Color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *NRGBA64) SetNRGBA64(x, y int, c NRGBA64Color) {
|
||||||
|
if !p.Rect.Contains(Point{x, y}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Pix[y*p.Stride+x] = c
|
||||||
|
}
|
||||||
|
|
||||||
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
||||||
func (p *NRGBA64) Opaque() bool {
|
func (p *NRGBA64) Opaque() bool {
|
||||||
if p.Rect.Empty() {
|
if p.Rect.Empty() {
|
||||||
@@ -252,13 +280,20 @@ func (p *Alpha) At(x, y int) Color {
|
|||||||
return p.Pix[y*p.Stride+x]
|
return p.Pix[y*p.Stride+x]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Alpha) Set(x, y int, c Color) {
|
func (p *Alpha) Set(x, y int, c AlphaColor) {
|
||||||
if !p.Rect.Contains(Point{x, y}) {
|
if !p.Rect.Contains(Point{x, y}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.Pix[y*p.Stride+x] = toAlphaColor(c).(AlphaColor)
|
p.Pix[y*p.Stride+x] = toAlphaColor(c).(AlphaColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Alpha) SetAlpha(x, y int, c AlphaColor) {
|
||||||
|
if !p.Rect.Contains(Point{x, y}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Pix[y*p.Stride+x] = c
|
||||||
|
}
|
||||||
|
|
||||||
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
||||||
func (p *Alpha) Opaque() bool {
|
func (p *Alpha) Opaque() bool {
|
||||||
if p.Rect.Empty() {
|
if p.Rect.Empty() {
|
||||||
@@ -311,6 +346,13 @@ func (p *Alpha16) Set(x, y int, c Color) {
|
|||||||
p.Pix[y*p.Stride+x] = toAlpha16Color(c).(Alpha16Color)
|
p.Pix[y*p.Stride+x] = toAlpha16Color(c).(Alpha16Color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Alpha16) SetAlpha16(x, y int, c Alpha16Color) {
|
||||||
|
if !p.Rect.Contains(Point{x, y}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Pix[y*p.Stride+x] = c
|
||||||
|
}
|
||||||
|
|
||||||
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
||||||
func (p *Alpha16) Opaque() bool {
|
func (p *Alpha16) Opaque() bool {
|
||||||
if p.Rect.Empty() {
|
if p.Rect.Empty() {
|
||||||
@@ -363,6 +405,13 @@ func (p *Gray) Set(x, y int, c Color) {
|
|||||||
p.Pix[y*p.Stride+x] = toGrayColor(c).(GrayColor)
|
p.Pix[y*p.Stride+x] = toGrayColor(c).(GrayColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Gray) SetGray(x, y int, c GrayColor) {
|
||||||
|
if !p.Rect.Contains(Point{x, y}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Pix[y*p.Stride+x] = c
|
||||||
|
}
|
||||||
|
|
||||||
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
||||||
func (p *Gray) Opaque() bool {
|
func (p *Gray) Opaque() bool {
|
||||||
return true
|
return true
|
||||||
@@ -401,6 +450,13 @@ func (p *Gray16) Set(x, y int, c Color) {
|
|||||||
p.Pix[y*p.Stride+x] = toGray16Color(c).(Gray16Color)
|
p.Pix[y*p.Stride+x] = toGray16Color(c).(Gray16Color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Gray16) SetGray16(x, y int, c Gray16Color) {
|
||||||
|
if !p.Rect.Contains(Point{x, y}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Pix[y*p.Stride+x] = c
|
||||||
|
}
|
||||||
|
|
||||||
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
// Opaque scans the entire image and returns whether or not it is fully opaque.
|
||||||
func (p *Gray16) Opaque() bool {
|
func (p *Gray16) Opaque() bool {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -391,6 +391,31 @@ func toYCbCr(m image.Image, p image.Point, yBlock, cbBlock, crBlock *block) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rgbaToYCbCr is a specialized version of toYCbCr for image.RGBA images.
|
||||||
|
func rgbaToYCbCr(m *image.RGBA, p image.Point, yBlock, cbBlock, crBlock *block) {
|
||||||
|
b := m.Bounds()
|
||||||
|
xmax := b.Max.X - 1
|
||||||
|
ymax := b.Max.Y - 1
|
||||||
|
for j := 0; j < 8; j++ {
|
||||||
|
sj := p.Y + j
|
||||||
|
if sj > ymax {
|
||||||
|
sj = ymax
|
||||||
|
}
|
||||||
|
yoff := sj * m.Stride
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
sx := p.X + i
|
||||||
|
if sx > xmax {
|
||||||
|
sx = xmax
|
||||||
|
}
|
||||||
|
col := &m.Pix[yoff+sx]
|
||||||
|
yy, cb, cr := ycbcr.RGBToYCbCr(col.R, col.G, col.B)
|
||||||
|
yBlock[8*j+i] = int(yy)
|
||||||
|
cbBlock[8*j+i] = int(cb)
|
||||||
|
crBlock[8*j+i] = int(cr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// scale scales the 16x16 region represented by the 4 src blocks to the 8x8
|
// scale scales the 16x16 region represented by the 4 src blocks to the 8x8
|
||||||
// dst block.
|
// dst block.
|
||||||
func scale(dst *block, src *[4]block) {
|
func scale(dst *block, src *[4]block) {
|
||||||
@@ -431,13 +456,18 @@ func (e *encoder) writeSOS(m image.Image) {
|
|||||||
prevDCY, prevDCCb, prevDCCr int
|
prevDCY, prevDCCb, prevDCCr int
|
||||||
)
|
)
|
||||||
bounds := m.Bounds()
|
bounds := m.Bounds()
|
||||||
|
rgba, _ := m.(*image.RGBA)
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y += 16 {
|
for y := bounds.Min.Y; y < bounds.Max.Y; y += 16 {
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x += 16 {
|
for x := bounds.Min.X; x < bounds.Max.X; x += 16 {
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
xOff := (i & 1) * 8
|
xOff := (i & 1) * 8
|
||||||
yOff := (i & 2) * 4
|
yOff := (i & 2) * 4
|
||||||
p := image.Point{x + xOff, y + yOff}
|
p := image.Point{x + xOff, y + yOff}
|
||||||
toYCbCr(m, p, &yBlock, &cbBlock[i], &crBlock[i])
|
if rgba != nil {
|
||||||
|
rgbaToYCbCr(rgba, p, &yBlock, &cbBlock[i], &crBlock[i])
|
||||||
|
} else {
|
||||||
|
toYCbCr(m, p, &yBlock, &cbBlock[i], &crBlock[i])
|
||||||
|
}
|
||||||
prevDCY = e.writeBlock(&yBlock, 0, prevDCY)
|
prevDCY = e.writeBlock(&yBlock, 0, prevDCY)
|
||||||
}
|
}
|
||||||
scale(&cBlock, &cbBlock)
|
scale(&cBlock, &cbBlock)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"image"
|
"image"
|
||||||
"image/png"
|
"image/png"
|
||||||
|
"io/ioutil"
|
||||||
|
"rand"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -85,3 +87,29 @@ func TestWriter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeRGBOpaque(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
img := image.NewRGBA(640, 480)
|
||||||
|
// Set all pixels to 0xFF alpha to force opaque mode.
|
||||||
|
bo := img.Bounds()
|
||||||
|
rnd := rand.New(rand.NewSource(123))
|
||||||
|
for y := bo.Min.Y; y < bo.Max.Y; y++ {
|
||||||
|
for x := bo.Min.X; x < bo.Max.X; x++ {
|
||||||
|
img.Set(x, y, image.RGBAColor{
|
||||||
|
uint8(rnd.Intn(256)),
|
||||||
|
uint8(rnd.Intn(256)),
|
||||||
|
uint8(rnd.Intn(256)),
|
||||||
|
255})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !img.Opaque() {
|
||||||
|
panic("expected image to be opaque")
|
||||||
|
}
|
||||||
|
b.SetBytes(640 * 480 * 4)
|
||||||
|
b.StartTimer()
|
||||||
|
options := &Options{Quality: 90}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Encode(ioutil.Discard, img, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ func (d *decoder) idatReader(idat io.Reader) (image.Image, os.Error) {
|
|||||||
for x := 0; x < d.width; x += 8 {
|
for x := 0; x < d.width; x += 8 {
|
||||||
b := cdat[x/8]
|
b := cdat[x/8]
|
||||||
for x2 := 0; x2 < 8 && x+x2 < d.width; x2++ {
|
for x2 := 0; x2 < 8 && x+x2 < d.width; x2++ {
|
||||||
gray.Set(x+x2, y, image.GrayColor{(b >> 7) * 0xff})
|
gray.SetGray(x+x2, y, image.GrayColor{(b >> 7) * 0xff})
|
||||||
b <<= 1
|
b <<= 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,7 +386,7 @@ func (d *decoder) idatReader(idat io.Reader) (image.Image, os.Error) {
|
|||||||
for x := 0; x < d.width; x += 4 {
|
for x := 0; x < d.width; x += 4 {
|
||||||
b := cdat[x/4]
|
b := cdat[x/4]
|
||||||
for x2 := 0; x2 < 4 && x+x2 < d.width; x2++ {
|
for x2 := 0; x2 < 4 && x+x2 < d.width; x2++ {
|
||||||
gray.Set(x+x2, y, image.GrayColor{(b >> 6) * 0x55})
|
gray.SetGray(x+x2, y, image.GrayColor{(b >> 6) * 0x55})
|
||||||
b <<= 2
|
b <<= 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -394,22 +394,22 @@ func (d *decoder) idatReader(idat io.Reader) (image.Image, os.Error) {
|
|||||||
for x := 0; x < d.width; x += 2 {
|
for x := 0; x < d.width; x += 2 {
|
||||||
b := cdat[x/2]
|
b := cdat[x/2]
|
||||||
for x2 := 0; x2 < 2 && x+x2 < d.width; x2++ {
|
for x2 := 0; x2 < 2 && x+x2 < d.width; x2++ {
|
||||||
gray.Set(x+x2, y, image.GrayColor{(b >> 4) * 0x11})
|
gray.SetGray(x+x2, y, image.GrayColor{(b >> 4) * 0x11})
|
||||||
b <<= 4
|
b <<= 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case cbG8:
|
case cbG8:
|
||||||
for x := 0; x < d.width; x++ {
|
for x := 0; x < d.width; x++ {
|
||||||
gray.Set(x, y, image.GrayColor{cdat[x]})
|
gray.SetGray(x, y, image.GrayColor{cdat[x]})
|
||||||
}
|
}
|
||||||
case cbGA8:
|
case cbGA8:
|
||||||
for x := 0; x < d.width; x++ {
|
for x := 0; x < d.width; x++ {
|
||||||
ycol := cdat[2*x+0]
|
ycol := cdat[2*x+0]
|
||||||
nrgba.Set(x, y, image.NRGBAColor{ycol, ycol, ycol, cdat[2*x+1]})
|
nrgba.SetNRGBA(x, y, image.NRGBAColor{ycol, ycol, ycol, cdat[2*x+1]})
|
||||||
}
|
}
|
||||||
case cbTC8:
|
case cbTC8:
|
||||||
for x := 0; x < d.width; x++ {
|
for x := 0; x < d.width; x++ {
|
||||||
rgba.Set(x, y, image.RGBAColor{cdat[3*x+0], cdat[3*x+1], cdat[3*x+2], 0xff})
|
rgba.SetRGBA(x, y, image.RGBAColor{cdat[3*x+0], cdat[3*x+1], cdat[3*x+2], 0xff})
|
||||||
}
|
}
|
||||||
case cbP1:
|
case cbP1:
|
||||||
for x := 0; x < d.width; x += 8 {
|
for x := 0; x < d.width; x += 8 {
|
||||||
@@ -456,25 +456,25 @@ func (d *decoder) idatReader(idat io.Reader) (image.Image, os.Error) {
|
|||||||
}
|
}
|
||||||
case cbTCA8:
|
case cbTCA8:
|
||||||
for x := 0; x < d.width; x++ {
|
for x := 0; x < d.width; x++ {
|
||||||
nrgba.Set(x, y, image.NRGBAColor{cdat[4*x+0], cdat[4*x+1], cdat[4*x+2], cdat[4*x+3]})
|
nrgba.SetNRGBA(x, y, image.NRGBAColor{cdat[4*x+0], cdat[4*x+1], cdat[4*x+2], cdat[4*x+3]})
|
||||||
}
|
}
|
||||||
case cbG16:
|
case cbG16:
|
||||||
for x := 0; x < d.width; x++ {
|
for x := 0; x < d.width; x++ {
|
||||||
ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1])
|
ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1])
|
||||||
gray16.Set(x, y, image.Gray16Color{ycol})
|
gray16.SetGray16(x, y, image.Gray16Color{ycol})
|
||||||
}
|
}
|
||||||
case cbGA16:
|
case cbGA16:
|
||||||
for x := 0; x < d.width; x++ {
|
for x := 0; x < d.width; x++ {
|
||||||
ycol := uint16(cdat[4*x+0])<<8 | uint16(cdat[4*x+1])
|
ycol := uint16(cdat[4*x+0])<<8 | uint16(cdat[4*x+1])
|
||||||
acol := uint16(cdat[4*x+2])<<8 | uint16(cdat[4*x+3])
|
acol := uint16(cdat[4*x+2])<<8 | uint16(cdat[4*x+3])
|
||||||
nrgba64.Set(x, y, image.NRGBA64Color{ycol, ycol, ycol, acol})
|
nrgba64.SetNRGBA64(x, y, image.NRGBA64Color{ycol, ycol, ycol, acol})
|
||||||
}
|
}
|
||||||
case cbTC16:
|
case cbTC16:
|
||||||
for x := 0; x < d.width; x++ {
|
for x := 0; x < d.width; x++ {
|
||||||
rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1])
|
rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1])
|
||||||
gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3])
|
gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3])
|
||||||
bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5])
|
bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5])
|
||||||
rgba64.Set(x, y, image.RGBA64Color{rcol, gcol, bcol, 0xffff})
|
rgba64.SetRGBA64(x, y, image.RGBA64Color{rcol, gcol, bcol, 0xffff})
|
||||||
}
|
}
|
||||||
case cbTCA16:
|
case cbTCA16:
|
||||||
for x := 0; x < d.width; x++ {
|
for x := 0; x < d.width; x++ {
|
||||||
@@ -482,7 +482,7 @@ func (d *decoder) idatReader(idat io.Reader) (image.Image, os.Error) {
|
|||||||
gcol := uint16(cdat[8*x+2])<<8 | uint16(cdat[8*x+3])
|
gcol := uint16(cdat[8*x+2])<<8 | uint16(cdat[8*x+3])
|
||||||
bcol := uint16(cdat[8*x+4])<<8 | uint16(cdat[8*x+5])
|
bcol := uint16(cdat[8*x+4])<<8 | uint16(cdat[8*x+5])
|
||||||
acol := uint16(cdat[8*x+6])<<8 | uint16(cdat[8*x+7])
|
acol := uint16(cdat[8*x+6])<<8 | uint16(cdat[8*x+7])
|
||||||
nrgba64.Set(x, y, image.NRGBA64Color{rcol, gcol, bcol, acol})
|
nrgba64.SetNRGBA64(x, y, image.NRGBA64Color{rcol, gcol, bcol, acol})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -263,7 +263,12 @@ func writeImage(w io.Writer, m image.Image, cb int) os.Error {
|
|||||||
defer zw.Close()
|
defer zw.Close()
|
||||||
|
|
||||||
bpp := 0 // Bytes per pixel.
|
bpp := 0 // Bytes per pixel.
|
||||||
|
|
||||||
|
// Used by fast paths for common image types
|
||||||
var paletted *image.Paletted
|
var paletted *image.Paletted
|
||||||
|
var rgba *image.RGBA
|
||||||
|
rgba, _ = m.(*image.RGBA)
|
||||||
|
|
||||||
switch cb {
|
switch cb {
|
||||||
case cbG8:
|
case cbG8:
|
||||||
bpp = 1
|
bpp = 1
|
||||||
@@ -303,12 +308,24 @@ func writeImage(w io.Writer, m image.Image, cb int) os.Error {
|
|||||||
cr[0][x+1] = c.Y
|
cr[0][x+1] = c.Y
|
||||||
}
|
}
|
||||||
case cbTC8:
|
case cbTC8:
|
||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
// We have previously verified that the alpha value is fully opaque.
|
||||||
// We have previously verified that the alpha value is fully opaque.
|
cr0 := cr[0]
|
||||||
r, g, b, _ := m.At(x, y).RGBA()
|
if rgba != nil {
|
||||||
cr[0][3*x+1] = uint8(r >> 8)
|
yoff := y * rgba.Stride
|
||||||
cr[0][3*x+2] = uint8(g >> 8)
|
xoff := 3*b.Min.X + 1
|
||||||
cr[0][3*x+3] = uint8(b >> 8)
|
for _, color := range rgba.Pix[yoff+b.Min.X : yoff+b.Max.X] {
|
||||||
|
cr0[xoff] = color.R
|
||||||
|
cr0[xoff+1] = color.G
|
||||||
|
cr0[xoff+2] = color.B
|
||||||
|
xoff += 3
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
|
r, g, b, _ := m.At(x, y).RGBA()
|
||||||
|
cr0[3*x+1] = uint8(r >> 8)
|
||||||
|
cr0[3*x+2] = uint8(g >> 8)
|
||||||
|
cr0[3*x+3] = uint8(b >> 8)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case cbP8:
|
case cbP8:
|
||||||
rowOffset := y * paletted.Stride
|
rowOffset := y * paletted.Stride
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
package png
|
package png
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -81,10 +81,42 @@ func BenchmarkEncodePaletted(b *testing.B) {
|
|||||||
image.RGBAColor{0, 0, 0, 255},
|
image.RGBAColor{0, 0, 0, 255},
|
||||||
image.RGBAColor{255, 255, 255, 255},
|
image.RGBAColor{255, 255, 255, 255},
|
||||||
})
|
})
|
||||||
|
b.SetBytes(640 * 480 * 1)
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
buffer := new(bytes.Buffer)
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
buffer.Reset()
|
Encode(ioutil.Discard, img)
|
||||||
Encode(buffer, img)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeRGBOpaque(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
img := image.NewRGBA(640, 480)
|
||||||
|
// Set all pixels to 0xFF alpha to force opaque mode.
|
||||||
|
bo := img.Bounds()
|
||||||
|
for y := bo.Min.Y; y < bo.Max.Y; y++ {
|
||||||
|
for x := bo.Min.X; x < bo.Max.X; x++ {
|
||||||
|
img.Set(x, y, image.RGBAColor{0, 0, 0, 255})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !img.Opaque() {
|
||||||
|
panic("expected image to be opaque")
|
||||||
|
}
|
||||||
|
b.SetBytes(640 * 480 * 4)
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Encode(ioutil.Discard, img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeRGBA(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
img := image.NewRGBA(640, 480)
|
||||||
|
if img.Opaque() {
|
||||||
|
panic("expected image to not be opaque")
|
||||||
|
}
|
||||||
|
b.SetBytes(640 * 480 * 4)
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Encode(ioutil.Discard, img)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ package multipart
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"mime"
|
"mime"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var headerRegexp *regexp.Regexp = regexp.MustCompile("^([a-zA-Z0-9\\-]+): *([^\r\n]+)")
|
var headerRegexp *regexp.Regexp = regexp.MustCompile("^([a-zA-Z0-9\\-]+): *([^\r\n]+)")
|
||||||
@@ -79,25 +79,28 @@ func (p *Part) FormName() string {
|
|||||||
// NewReader creates a new multipart Reader reading from r using the
|
// NewReader creates a new multipart Reader reading from r using the
|
||||||
// given MIME boundary.
|
// given MIME boundary.
|
||||||
func NewReader(reader io.Reader, boundary string) Reader {
|
func NewReader(reader io.Reader, boundary string) Reader {
|
||||||
|
b := []byte("\r\n--" + boundary + "--")
|
||||||
return &multiReader{
|
return &multiReader{
|
||||||
boundary: boundary,
|
bufReader: bufio.NewReader(reader),
|
||||||
dashBoundary: "--" + boundary,
|
|
||||||
endLine: "--" + boundary + "--",
|
nlDashBoundary: b[:len(b)-2],
|
||||||
bufReader: bufio.NewReader(reader),
|
dashBoundaryDash: b[2:],
|
||||||
|
dashBoundary: b[2 : len(b)-2],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementation ....
|
// Implementation ....
|
||||||
|
|
||||||
func newPart(mr *multiReader) (bp *Part, err os.Error) {
|
func newPart(mr *multiReader) (*Part, os.Error) {
|
||||||
bp = new(Part)
|
bp := &Part{
|
||||||
bp.Header = make(map[string][]string)
|
Header: make(map[string][]string),
|
||||||
bp.mr = mr
|
mr: mr,
|
||||||
bp.buffer = new(bytes.Buffer)
|
buffer: new(bytes.Buffer),
|
||||||
if err = bp.populateHeaders(); err != nil {
|
|
||||||
bp = nil
|
|
||||||
}
|
}
|
||||||
return
|
if err := bp.populateHeaders(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *Part) populateHeaders() os.Error {
|
func (bp *Part) populateHeaders() os.Error {
|
||||||
@@ -122,44 +125,49 @@ func (bp *Part) populateHeaders() os.Error {
|
|||||||
// Read reads the body of a part, after its headers and before the
|
// Read reads the body of a part, after its headers and before the
|
||||||
// next part (if any) begins.
|
// next part (if any) begins.
|
||||||
func (bp *Part) Read(p []byte) (n int, err os.Error) {
|
func (bp *Part) Read(p []byte) (n int, err os.Error) {
|
||||||
for {
|
if bp.buffer.Len() >= len(p) {
|
||||||
if bp.buffer.Len() >= len(p) {
|
// Internal buffer of unconsumed data is large enough for
|
||||||
// Internal buffer of unconsumed data is large enough for
|
// the read request. No need to parse more at the moment.
|
||||||
// the read request. No need to parse more at the moment.
|
return bp.buffer.Read(p)
|
||||||
break
|
|
||||||
}
|
|
||||||
if !bp.mr.ensureBufferedLine() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
if bp.mr.bufferedLineIsBoundary() {
|
|
||||||
// Don't consume this line
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write all of this line, except the final CRLF
|
|
||||||
s := *bp.mr.bufferedLine
|
|
||||||
if strings.HasSuffix(s, "\r\n") {
|
|
||||||
bp.mr.consumeLine()
|
|
||||||
if !bp.mr.ensureBufferedLine() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
if bp.mr.bufferedLineIsBoundary() {
|
|
||||||
// The final \r\n isn't ours. It logically belongs
|
|
||||||
// to the boundary line which follows.
|
|
||||||
bp.buffer.WriteString(s[0 : len(s)-2])
|
|
||||||
} else {
|
|
||||||
bp.buffer.WriteString(s)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(s, "\n") {
|
|
||||||
bp.buffer.WriteString(s)
|
|
||||||
bp.mr.consumeLine()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return 0, os.NewError("multipart parse error during Read; unexpected line: " + s)
|
|
||||||
}
|
}
|
||||||
return bp.buffer.Read(p)
|
peek, err := bp.mr.bufReader.Peek(4096) // TODO(bradfitz): add buffer size accessor
|
||||||
|
unexpectedEof := err == os.EOF
|
||||||
|
if err != nil && !unexpectedEof {
|
||||||
|
return 0, fmt.Errorf("multipart: Part Read: %v", err)
|
||||||
|
}
|
||||||
|
if peek == nil {
|
||||||
|
panic("nil peek buf")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search the peek buffer for "\r\n--boundary". If found,
|
||||||
|
// consume everything up to the boundary. If not, consume only
|
||||||
|
// as much of the peek buffer as cannot hold the boundary
|
||||||
|
// string.
|
||||||
|
nCopy := 0
|
||||||
|
foundBoundary := false
|
||||||
|
if idx := bytes.Index(peek, bp.mr.nlDashBoundary); idx != -1 {
|
||||||
|
nCopy = idx
|
||||||
|
foundBoundary = true
|
||||||
|
} else if safeCount := len(peek) - len(bp.mr.nlDashBoundary); safeCount > 0 {
|
||||||
|
nCopy = safeCount
|
||||||
|
} else if unexpectedEof {
|
||||||
|
// If we've run out of peek buffer and the boundary
|
||||||
|
// wasn't found (and can't possibly fit), we must have
|
||||||
|
// hit the end of the file unexpectedly.
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if nCopy > 0 {
|
||||||
|
if _, err := io.Copyn(bp.buffer, bp.mr.bufReader, int64(nCopy)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, err = bp.buffer.Read(p)
|
||||||
|
if err == os.EOF && !foundBoundary {
|
||||||
|
// If the boundary hasn't been reached there's more to
|
||||||
|
// read, so don't pass through an EOF from the buffer
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *Part) Close() os.Error {
|
func (bp *Part) Close() os.Error {
|
||||||
@@ -168,46 +176,12 @@ func (bp *Part) Close() os.Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type multiReader struct {
|
type multiReader struct {
|
||||||
boundary string
|
bufReader *bufio.Reader
|
||||||
dashBoundary string // --boundary
|
|
||||||
endLine string // --boundary--
|
|
||||||
|
|
||||||
bufferedLine *string
|
|
||||||
|
|
||||||
bufReader *bufio.Reader
|
|
||||||
currentPart *Part
|
currentPart *Part
|
||||||
partsRead int
|
partsRead int
|
||||||
}
|
|
||||||
|
|
||||||
func (mr *multiReader) eof() bool {
|
nlDashBoundary, dashBoundaryDash, dashBoundary []byte
|
||||||
return mr.bufferedLine == nil &&
|
|
||||||
!mr.readLine()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mr *multiReader) readLine() bool {
|
|
||||||
lineBytes, err := mr.bufReader.ReadSlice('\n')
|
|
||||||
if err != nil {
|
|
||||||
// TODO: care about err being EOF or not?
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
line := string(lineBytes)
|
|
||||||
mr.bufferedLine = &line
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mr *multiReader) bufferedLineIsBoundary() bool {
|
|
||||||
return strings.HasPrefix(*mr.bufferedLine, mr.dashBoundary)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mr *multiReader) ensureBufferedLine() bool {
|
|
||||||
if mr.bufferedLine == nil {
|
|
||||||
return mr.readLine()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mr *multiReader) consumeLine() {
|
|
||||||
mr.bufferedLine = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mr *multiReader) NextPart() (*Part, os.Error) {
|
func (mr *multiReader) NextPart() (*Part, os.Error) {
|
||||||
@@ -215,13 +189,14 @@ func (mr *multiReader) NextPart() (*Part, os.Error) {
|
|||||||
mr.currentPart.Close()
|
mr.currentPart.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expectNewPart := false
|
||||||
for {
|
for {
|
||||||
if mr.eof() {
|
line, err := mr.bufReader.ReadSlice('\n')
|
||||||
return nil, io.ErrUnexpectedEOF
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("multipart: NextPart: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isBoundaryDelimiterLine(*mr.bufferedLine, mr.dashBoundary) {
|
if mr.isBoundaryDelimiterLine(line) {
|
||||||
mr.consumeLine()
|
|
||||||
mr.partsRead++
|
mr.partsRead++
|
||||||
bp, err := newPart(mr)
|
bp, err := newPart(mr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -231,55 +206,67 @@ func (mr *multiReader) NextPart() (*Part, os.Error) {
|
|||||||
return bp, nil
|
return bp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasPrefixThenNewline(*mr.bufferedLine, mr.endLine) {
|
if hasPrefixThenNewline(line, mr.dashBoundaryDash) {
|
||||||
mr.consumeLine()
|
|
||||||
// Expected EOF (no error)
|
// Expected EOF (no error)
|
||||||
|
// TODO(bradfitz): should return an os.EOF error here, not using nil for errors
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if expectNewPart {
|
||||||
|
return nil, fmt.Errorf("multipart: expecting a new Part; got line %q", string(line))
|
||||||
|
}
|
||||||
|
|
||||||
if mr.partsRead == 0 {
|
if mr.partsRead == 0 {
|
||||||
// skip line
|
// skip line
|
||||||
mr.consumeLine()
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, os.NewError("Unexpected line in Next().")
|
if bytes.Equal(line, []byte("\r\n")) {
|
||||||
|
// Consume the "\r\n" separator between the
|
||||||
|
// body of the previous part and the boundary
|
||||||
|
// line we now expect will follow. (either a
|
||||||
|
// new part or the end boundary)
|
||||||
|
expectNewPart = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("multipart: unexpected line in Next(): %q", line)
|
||||||
}
|
}
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func isBoundaryDelimiterLine(line, dashPrefix string) bool {
|
func (mr *multiReader) isBoundaryDelimiterLine(line []byte) bool {
|
||||||
// http://tools.ietf.org/html/rfc2046#section-5.1
|
// http://tools.ietf.org/html/rfc2046#section-5.1
|
||||||
// The boundary delimiter line is then defined as a line
|
// The boundary delimiter line is then defined as a line
|
||||||
// consisting entirely of two hyphen characters ("-",
|
// consisting entirely of two hyphen characters ("-",
|
||||||
// decimal value 45) followed by the boundary parameter
|
// decimal value 45) followed by the boundary parameter
|
||||||
// value from the Content-Type header field, optional linear
|
// value from the Content-Type header field, optional linear
|
||||||
// whitespace, and a terminating CRLF.
|
// whitespace, and a terminating CRLF.
|
||||||
if !strings.HasPrefix(line, dashPrefix) {
|
if !bytes.HasPrefix(line, mr.dashBoundary) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(line, "\r\n") {
|
if bytes.HasSuffix(line, []byte("\r\n")) {
|
||||||
return onlyHorizontalWhitespace(line[len(dashPrefix) : len(line)-2])
|
return onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-2])
|
||||||
}
|
}
|
||||||
// Violate the spec and also support newlines without the
|
// Violate the spec and also support newlines without the
|
||||||
// carriage return...
|
// carriage return...
|
||||||
if strings.HasSuffix(line, "\n") {
|
if bytes.HasSuffix(line, []byte("\n")) {
|
||||||
return onlyHorizontalWhitespace(line[len(dashPrefix) : len(line)-1])
|
return onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-1])
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func onlyHorizontalWhitespace(s string) bool {
|
func onlyHorizontalWhitespace(s []byte) bool {
|
||||||
for i := 0; i < len(s); i++ {
|
for _, b := range s {
|
||||||
if s[i] != ' ' && s[i] != '\t' {
|
if b != ' ' && b != '\t' {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasPrefixThenNewline(s, prefix string) bool {
|
func hasPrefixThenNewline(s, prefix []byte) bool {
|
||||||
return strings.HasPrefix(s, prefix) &&
|
return bytes.HasPrefix(s, prefix) &&
|
||||||
(len(s) == len(prefix)+1 && strings.HasSuffix(s, "\n") ||
|
(len(s) == len(prefix)+1 && s[len(s)-1] == '\n' ||
|
||||||
len(s) == len(prefix)+2 && strings.HasSuffix(s, "\r\n"))
|
len(s) == len(prefix)+2 && bytes.HasSuffix(s, []byte("\r\n")))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,38 +8,37 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"json"
|
"json"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHorizontalWhitespace(t *testing.T) {
|
func TestHorizontalWhitespace(t *testing.T) {
|
||||||
if !onlyHorizontalWhitespace(" \t") {
|
if !onlyHorizontalWhitespace([]byte(" \t")) {
|
||||||
t.Error("expected pass")
|
t.Error("expected pass")
|
||||||
}
|
}
|
||||||
if onlyHorizontalWhitespace("foo bar") {
|
if onlyHorizontalWhitespace([]byte("foo bar")) {
|
||||||
t.Error("expected failure")
|
t.Error("expected failure")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoundaryLine(t *testing.T) {
|
func TestBoundaryLine(t *testing.T) {
|
||||||
boundary := "myBoundary"
|
mr := NewReader(strings.NewReader(""), "myBoundary").(*multiReader)
|
||||||
prefix := "--" + boundary
|
if !mr.isBoundaryDelimiterLine([]byte("--myBoundary\r\n")) {
|
||||||
if !isBoundaryDelimiterLine("--myBoundary\r\n", prefix) {
|
|
||||||
t.Error("expected")
|
t.Error("expected")
|
||||||
}
|
}
|
||||||
if !isBoundaryDelimiterLine("--myBoundary \r\n", prefix) {
|
if !mr.isBoundaryDelimiterLine([]byte("--myBoundary \r\n")) {
|
||||||
t.Error("expected")
|
t.Error("expected")
|
||||||
}
|
}
|
||||||
if !isBoundaryDelimiterLine("--myBoundary \n", prefix) {
|
if !mr.isBoundaryDelimiterLine([]byte("--myBoundary \n")) {
|
||||||
t.Error("expected")
|
t.Error("expected")
|
||||||
}
|
}
|
||||||
if isBoundaryDelimiterLine("--myBoundary bogus \n", prefix) {
|
if mr.isBoundaryDelimiterLine([]byte("--myBoundary bogus \n")) {
|
||||||
t.Error("expected fail")
|
t.Error("expected fail")
|
||||||
}
|
}
|
||||||
if isBoundaryDelimiterLine("--myBoundary bogus--", prefix) {
|
if mr.isBoundaryDelimiterLine([]byte("--myBoundary bogus--")) {
|
||||||
t.Error("expected fail")
|
t.Error("expected fail")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +78,9 @@ func TestFormName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultipart(t *testing.T) {
|
var longLine = strings.Repeat("\n\n\r\r\r\n\r\000", (1<<20)/8)
|
||||||
|
|
||||||
|
func testMultipartBody() string {
|
||||||
testBody := `
|
testBody := `
|
||||||
This is a multi-part message. This line is ignored.
|
This is a multi-part message. This line is ignored.
|
||||||
--MyBoundary
|
--MyBoundary
|
||||||
@@ -90,6 +91,10 @@ foo-bar: baz
|
|||||||
My value
|
My value
|
||||||
The end.
|
The end.
|
||||||
--MyBoundary
|
--MyBoundary
|
||||||
|
name: bigsection
|
||||||
|
|
||||||
|
[longline]
|
||||||
|
--MyBoundary
|
||||||
Header1: value1b
|
Header1: value1b
|
||||||
HEADER2: value2b
|
HEADER2: value2b
|
||||||
foo-bar: bazb
|
foo-bar: bazb
|
||||||
@@ -102,11 +107,26 @@ Line 3 ends in a newline, but just one.
|
|||||||
|
|
||||||
never read data
|
never read data
|
||||||
--MyBoundary--
|
--MyBoundary--
|
||||||
`
|
|
||||||
testBody = regexp.MustCompile("\n").ReplaceAllString(testBody, "\r\n")
|
|
||||||
bodyReader := strings.NewReader(testBody)
|
|
||||||
|
|
||||||
reader := NewReader(bodyReader, "MyBoundary")
|
|
||||||
|
useless trailer
|
||||||
|
`
|
||||||
|
testBody = strings.Replace(testBody, "\n", "\r\n", -1)
|
||||||
|
return strings.Replace(testBody, "[longline]", longLine, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipart(t *testing.T) {
|
||||||
|
bodyReader := strings.NewReader(testMultipartBody())
|
||||||
|
testMultipart(t, bodyReader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipartSlowInput(t *testing.T) {
|
||||||
|
bodyReader := strings.NewReader(testMultipartBody())
|
||||||
|
testMultipart(t, &slowReader{bodyReader})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMultipart(t *testing.T, r io.Reader) {
|
||||||
|
reader := NewReader(r, "MyBoundary")
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
// Part1
|
// Part1
|
||||||
@@ -125,38 +145,64 @@ never read data
|
|||||||
t.Error("Expected Foo-Bar: baz")
|
t.Error("Expected Foo-Bar: baz")
|
||||||
}
|
}
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
io.Copy(buf, part)
|
if _, err := io.Copy(buf, part); err != nil {
|
||||||
|
t.Errorf("part 1 copy: %v", err)
|
||||||
|
}
|
||||||
expectEq(t, "My value\r\nThe end.",
|
expectEq(t, "My value\r\nThe end.",
|
||||||
buf.String(), "Value of first part")
|
buf.String(), "Value of first part")
|
||||||
|
|
||||||
// Part2
|
// Part2
|
||||||
part, err = reader.NextPart()
|
part, err = reader.NextPart()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected part2; got: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e, g := "bigsection", part.Header.Get("name"); e != g {
|
||||||
|
t.Errorf("part2's name header: expected %q, got %q", e, g)
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
if _, err := io.Copy(buf, part); err != nil {
|
||||||
|
t.Errorf("part 2 copy: %v", err)
|
||||||
|
}
|
||||||
|
s := buf.String()
|
||||||
|
if len(s) != len(longLine) {
|
||||||
|
t.Errorf("part2 body expected long line of length %d; got length %d",
|
||||||
|
len(longLine), len(s))
|
||||||
|
}
|
||||||
|
if s != longLine {
|
||||||
|
t.Errorf("part2 long body didn't match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part3
|
||||||
|
part, err = reader.NextPart()
|
||||||
if part == nil || err != nil {
|
if part == nil || err != nil {
|
||||||
t.Error("Expected part2")
|
t.Error("Expected part3")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if part.Header.Get("foo-bar") != "bazb" {
|
if part.Header.Get("foo-bar") != "bazb" {
|
||||||
t.Error("Expected foo-bar: bazb")
|
t.Error("Expected foo-bar: bazb")
|
||||||
}
|
}
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
io.Copy(buf, part)
|
if _, err := io.Copy(buf, part); err != nil {
|
||||||
|
t.Errorf("part 3 copy: %v", err)
|
||||||
|
}
|
||||||
expectEq(t, "Line 1\r\nLine 2\r\nLine 3 ends in a newline, but just one.\r\n",
|
expectEq(t, "Line 1\r\nLine 2\r\nLine 3 ends in a newline, but just one.\r\n",
|
||||||
buf.String(), "Value of second part")
|
buf.String(), "body of part 3")
|
||||||
|
|
||||||
// Part3
|
// Part4
|
||||||
part, err = reader.NextPart()
|
part, err = reader.NextPart()
|
||||||
if part == nil || err != nil {
|
if part == nil || err != nil {
|
||||||
t.Error("Expected part3 without errors")
|
t.Error("Expected part 4 without errors")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-existent part4
|
// Non-existent part5
|
||||||
part, err = reader.NextPart()
|
part, err = reader.NextPart()
|
||||||
if part != nil {
|
if part != nil {
|
||||||
t.Error("Didn't expect a third part.")
|
t.Error("Didn't expect a fifth part.")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error getting third part: %v", err)
|
t.Errorf("Unexpected error getting fifth part: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,3 +283,36 @@ func TestLineLimit(t *testing.T) {
|
|||||||
t.Errorf("expected to read < %d bytes; read %d", maxReadThreshold, mr.n)
|
t.Errorf("expected to read < %d bytes; read %d", maxReadThreshold, mr.n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMultipartTruncated(t *testing.T) {
|
||||||
|
testBody := `
|
||||||
|
This is a multi-part message. This line is ignored.
|
||||||
|
--MyBoundary
|
||||||
|
foo-bar: baz
|
||||||
|
|
||||||
|
Oh no, premature EOF!
|
||||||
|
`
|
||||||
|
body := strings.Replace(testBody, "\n", "\r\n", -1)
|
||||||
|
bodyReader := strings.NewReader(body)
|
||||||
|
r := NewReader(bodyReader, "MyBoundary")
|
||||||
|
|
||||||
|
part, err := r.NextPart()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("didn't get a part")
|
||||||
|
}
|
||||||
|
_, err = io.Copy(ioutil.Discard, part)
|
||||||
|
if err != io.ErrUnexpectedEOF {
|
||||||
|
t.Fatalf("expected error io.ErrUnexpectedEOF; got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type slowReader struct {
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slowReader) Read(p []byte) (int, os.Error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return s.r.Read(p)
|
||||||
|
}
|
||||||
|
return s.r.Read(p[:1])
|
||||||
|
}
|
||||||
|
|||||||
@@ -182,7 +182,9 @@ var valueTests = []pair{
|
|||||||
}),
|
}),
|
||||||
"struct { c chan *int32; d float32 }{chan *int32, 0}",
|
"struct { c chan *int32; d float32 }{chan *int32, 0}",
|
||||||
},
|
},
|
||||||
{new(struct{ c func(chan *integer, *int8) }),
|
{new(struct {
|
||||||
|
c func(chan *integer, *int8)
|
||||||
|
}),
|
||||||
"struct { c func(chan *reflect_test.integer, *int8) }{func(chan *reflect_test.integer, *int8)(0)}",
|
"struct { c func(chan *reflect_test.integer, *int8) }{func(chan *reflect_test.integer, *int8)(0)}",
|
||||||
},
|
},
|
||||||
{new(struct {
|
{new(struct {
|
||||||
@@ -732,6 +734,24 @@ func TestDeepEqualComplexStructInequality(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UnexpT struct {
|
||||||
|
m map[int]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeepEqualUnexportedMap(t *testing.T) {
|
||||||
|
// Check that DeepEqual can look at unexported fields.
|
||||||
|
x1 := UnexpT{map[int]int{1: 2}}
|
||||||
|
x2 := UnexpT{map[int]int{1: 2}}
|
||||||
|
if !DeepEqual(&x1, &x2) {
|
||||||
|
t.Error("DeepEqual(x1, x2) = false, want true")
|
||||||
|
}
|
||||||
|
|
||||||
|
y1 := UnexpT{map[int]int{2: 3}}
|
||||||
|
if DeepEqual(&x1, &y1) {
|
||||||
|
t.Error("DeepEqual(x1, y1) = true, want false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func check2ndField(x interface{}, offs uintptr, t *testing.T) {
|
func check2ndField(x interface{}, offs uintptr, t *testing.T) {
|
||||||
s := ValueOf(x)
|
s := ValueOf(x)
|
||||||
|
|||||||
@@ -958,14 +958,19 @@ func (v Value) MapIndex(key Value) Value {
|
|||||||
iv.mustBe(Map)
|
iv.mustBe(Map)
|
||||||
typ := iv.typ.toType()
|
typ := iv.typ.toType()
|
||||||
|
|
||||||
|
// Do not require ikey to be exported, so that DeepEqual
|
||||||
|
// and other programs can use all the keys returned by
|
||||||
|
// MapKeys as arguments to MapIndex. If either the map
|
||||||
|
// or the key is unexported, though, the result will be
|
||||||
|
// considered unexported.
|
||||||
|
|
||||||
ikey := key.internal()
|
ikey := key.internal()
|
||||||
ikey.mustBeExported()
|
|
||||||
ikey = convertForAssignment("reflect.Value.MapIndex", nil, typ.Key(), ikey)
|
ikey = convertForAssignment("reflect.Value.MapIndex", nil, typ.Key(), ikey)
|
||||||
if iv.word == 0 {
|
if iv.word == 0 {
|
||||||
return Value{}
|
return Value{}
|
||||||
}
|
}
|
||||||
|
|
||||||
flag := iv.flag & flagRO
|
flag := (iv.flag | ikey.flag) & flagRO
|
||||||
elemType := typ.Elem()
|
elemType := typ.Elem()
|
||||||
elemWord, ok := mapaccess(iv.word, ikey.word)
|
elemWord, ok := mapaccess(iv.word, ikey.word)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -258,11 +258,22 @@ TEXT cas<>(SB),7,$0
|
|||||||
TEXT runtime·cas(SB),7,$0
|
TEXT runtime·cas(SB),7,$0
|
||||||
MOVW valptr+0(FP), R2
|
MOVW valptr+0(FP), R2
|
||||||
MOVW old+4(FP), R0
|
MOVW old+4(FP), R0
|
||||||
|
casagain:
|
||||||
MOVW new+8(FP), R1
|
MOVW new+8(FP), R1
|
||||||
BL cas<>(SB)
|
BL cas<>(SB)
|
||||||
MOVW $0, R0
|
BCC cascheck
|
||||||
MOVW.CS $1, R0
|
MOVW $1, R0
|
||||||
RET
|
RET
|
||||||
|
cascheck:
|
||||||
|
// Kernel lies; double-check.
|
||||||
|
MOVW valptr+0(FP), R2
|
||||||
|
MOVW old+4(FP), R0
|
||||||
|
MOVW 0(R2), R3
|
||||||
|
CMP R0, R3
|
||||||
|
BEQ casagain
|
||||||
|
MOVW $0, R0
|
||||||
|
RET
|
||||||
|
|
||||||
|
|
||||||
TEXT runtime·casp(SB),7,$0
|
TEXT runtime·casp(SB),7,$0
|
||||||
B runtime·cas(SB)
|
B runtime·cas(SB)
|
||||||
|
|||||||
@@ -13,6 +13,12 @@
|
|||||||
// LR = return address
|
// LR = return address
|
||||||
// The function returns with CS true if the swap happened.
|
// The function returns with CS true if the swap happened.
|
||||||
// http://lxr.linux.no/linux+v2.6.37.2/arch/arm/kernel/entry-armv.S#L850
|
// http://lxr.linux.no/linux+v2.6.37.2/arch/arm/kernel/entry-armv.S#L850
|
||||||
|
// On older kernels (before 2.6.24) the function can incorrectly
|
||||||
|
// report a conflict, so we have to double-check the compare ourselves
|
||||||
|
// and retry if necessary.
|
||||||
|
//
|
||||||
|
// http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=b49c0f24cf6744a3f4fd09289fe7cade349dead5
|
||||||
|
//
|
||||||
TEXT cas<>(SB),7,$0
|
TEXT cas<>(SB),7,$0
|
||||||
MOVW $0xffff0fc0, PC
|
MOVW $0xffff0fc0, PC
|
||||||
|
|
||||||
@@ -23,12 +29,23 @@ TEXT ·CompareAndSwapInt32(SB),7,$0
|
|||||||
TEXT ·CompareAndSwapUint32(SB),7,$0
|
TEXT ·CompareAndSwapUint32(SB),7,$0
|
||||||
MOVW valptr+0(FP), R2
|
MOVW valptr+0(FP), R2
|
||||||
MOVW old+4(FP), R0
|
MOVW old+4(FP), R0
|
||||||
|
casagain:
|
||||||
MOVW new+8(FP), R1
|
MOVW new+8(FP), R1
|
||||||
BL cas<>(SB)
|
BL cas<>(SB)
|
||||||
MOVW $0, R0
|
BCC cascheck
|
||||||
MOVW.CS $1, R0
|
MOVW $1, R0
|
||||||
|
casret:
|
||||||
MOVW R0, ret+12(FP)
|
MOVW R0, ret+12(FP)
|
||||||
RET
|
RET
|
||||||
|
cascheck:
|
||||||
|
// Kernel lies; double-check.
|
||||||
|
MOVW valptr+0(FP), R2
|
||||||
|
MOVW old+4(FP), R0
|
||||||
|
MOVW 0(R2), R3
|
||||||
|
CMP R0, R3
|
||||||
|
BEQ casagain
|
||||||
|
MOVW $0, R0
|
||||||
|
B casret
|
||||||
|
|
||||||
TEXT ·CompareAndSwapUintptr(SB),7,$0
|
TEXT ·CompareAndSwapUintptr(SB),7,$0
|
||||||
B ·CompareAndSwapUint32(SB)
|
B ·CompareAndSwapUint32(SB)
|
||||||
|
|||||||
@@ -220,13 +220,10 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pv := val; pv.Kind() == reflect.Ptr {
|
if pv := val; pv.Kind() == reflect.Ptr {
|
||||||
if pv.Pointer() == 0 {
|
if pv.IsNil() {
|
||||||
zv := reflect.Zero(pv.Type().Elem())
|
pv.Set(reflect.New(pv.Type().Elem()))
|
||||||
pv.Set(zv.Addr())
|
|
||||||
val = zv
|
|
||||||
} else {
|
|
||||||
val = pv.Elem()
|
|
||||||
}
|
}
|
||||||
|
val = pv.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -329,47 +329,51 @@ func TestSyntax(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type allScalars struct {
|
type allScalars struct {
|
||||||
True1 bool
|
True1 bool
|
||||||
True2 bool
|
True2 bool
|
||||||
False1 bool
|
False1 bool
|
||||||
False2 bool
|
False2 bool
|
||||||
Int int
|
Int int
|
||||||
Int8 int8
|
Int8 int8
|
||||||
Int16 int16
|
Int16 int16
|
||||||
Int32 int32
|
Int32 int32
|
||||||
Int64 int64
|
Int64 int64
|
||||||
Uint int
|
Uint int
|
||||||
Uint8 uint8
|
Uint8 uint8
|
||||||
Uint16 uint16
|
Uint16 uint16
|
||||||
Uint32 uint32
|
Uint32 uint32
|
||||||
Uint64 uint64
|
Uint64 uint64
|
||||||
Uintptr uintptr
|
Uintptr uintptr
|
||||||
Float32 float32
|
Float32 float32
|
||||||
Float64 float64
|
Float64 float64
|
||||||
String string
|
String string
|
||||||
|
PtrString *string
|
||||||
}
|
}
|
||||||
|
|
||||||
var all = allScalars{
|
var all = allScalars{
|
||||||
True1: true,
|
True1: true,
|
||||||
True2: true,
|
True2: true,
|
||||||
False1: false,
|
False1: false,
|
||||||
False2: false,
|
False2: false,
|
||||||
Int: 1,
|
Int: 1,
|
||||||
Int8: -2,
|
Int8: -2,
|
||||||
Int16: 3,
|
Int16: 3,
|
||||||
Int32: -4,
|
Int32: -4,
|
||||||
Int64: 5,
|
Int64: 5,
|
||||||
Uint: 6,
|
Uint: 6,
|
||||||
Uint8: 7,
|
Uint8: 7,
|
||||||
Uint16: 8,
|
Uint16: 8,
|
||||||
Uint32: 9,
|
Uint32: 9,
|
||||||
Uint64: 10,
|
Uint64: 10,
|
||||||
Uintptr: 11,
|
Uintptr: 11,
|
||||||
Float32: 13.0,
|
Float32: 13.0,
|
||||||
Float64: 14.0,
|
Float64: 14.0,
|
||||||
String: "15",
|
String: "15",
|
||||||
|
PtrString: &sixteen,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sixteen = "16"
|
||||||
|
|
||||||
const testScalarsInput = `<allscalars>
|
const testScalarsInput = `<allscalars>
|
||||||
<true1>true</true1>
|
<true1>true</true1>
|
||||||
<true2>1</true2>
|
<true2>1</true2>
|
||||||
@@ -390,6 +394,7 @@ const testScalarsInput = `<allscalars>
|
|||||||
<float32>13.0</float32>
|
<float32>13.0</float32>
|
||||||
<float64>14.0</float64>
|
<float64>14.0</float64>
|
||||||
<string>15</string>
|
<string>15</string>
|
||||||
|
<ptrstring>16</ptrstring>
|
||||||
</allscalars>`
|
</allscalars>`
|
||||||
|
|
||||||
func TestAllScalars(t *testing.T) {
|
func TestAllScalars(t *testing.T) {
|
||||||
@@ -401,7 +406,7 @@ func TestAllScalars(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(a, all) {
|
if !reflect.DeepEqual(a, all) {
|
||||||
t.Errorf("expected %+v got %+v", all, a)
|
t.Errorf("have %+v want %+v", a, all)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user