Commit 9aa28f92 authored by Russ Cox's avatar Russ Cox

bufio:

	* avoid large copies
	* NewBufRead, NewBufWrite never fail
	* add BufReadWrite

io:
	* add io.Close

http, google/net/rpc:
	* add, use http.Conn.Hijack

R=r
DELTA=416  (202 added, 123 deleted, 91 changed)
OCL=24153
CL=24238
parent 535dcf77
...@@ -16,6 +16,8 @@ import ( ...@@ -16,6 +16,8 @@ import (
// - BufRead: ReadRune, UnreadRune ? // - BufRead: ReadRune, UnreadRune ?
// could make ReadRune generic if we dropped UnreadRune // could make ReadRune generic if we dropped UnreadRune
// - buffered output // - buffered output
// - would like to rename to Read, Write, but breaks
// embedding of these: would lose the Read, Write methods.
const ( const (
defaultBufSize = 4096 defaultBufSize = 4096
...@@ -44,6 +46,7 @@ type BufRead struct { ...@@ -44,6 +46,7 @@ type BufRead struct {
rd io.Read; rd io.Read;
r, w int; r, w int;
err *os.Error; err *os.Error;
lastbyte int;
} }
func NewBufReadSize(rd io.Read, size int) (b *BufRead, err *os.Error) { func NewBufReadSize(rd io.Read, size int) (b *BufRead, err *os.Error) {
...@@ -53,11 +56,17 @@ func NewBufReadSize(rd io.Read, size int) (b *BufRead, err *os.Error) { ...@@ -53,11 +56,17 @@ func NewBufReadSize(rd io.Read, size int) (b *BufRead, err *os.Error) {
b = new(BufRead); b = new(BufRead);
b.buf = make([]byte, size); b.buf = make([]byte, size);
b.rd = rd; b.rd = rd;
b.lastbyte = -1;
return b, nil return b, nil
} }
func NewBufRead(rd io.Read) (b *BufRead, err *os.Error) { func NewBufRead(rd io.Read) *BufRead {
return NewBufReadSize(rd, defaultBufSize); b, err := NewBufReadSize(rd, defaultBufSize);
if err != nil {
// cannot happen - defaultBufSize is a valid size
panic("bufio: NewBufRead: ", err.String());
}
return b;
} }
// Read a new chunk into the buffer. // Read a new chunk into the buffer.
...@@ -94,6 +103,23 @@ func (b *BufRead) Read(p []byte) (nn int, err *os.Error) { ...@@ -94,6 +103,23 @@ func (b *BufRead) Read(p []byte) (nn int, err *os.Error) {
for len(p) > 0 { for len(p) > 0 {
n := len(p); n := len(p);
if b.w == b.r { if b.w == b.r {
if len(p) >= len(b.buf) {
// Large read, empty buffer.
// Read directly into p to avoid copy.
n, b.err = b.rd.Read(p);
if n > 0 {
b.lastbyte = int(p[n-1]);
}
p = p[n:len(p)];
nn += n;
if b.err != nil {
return nn, b.err
}
if n == 0 {
return nn, EndOfFile
}
continue;
}
b.Fill(); b.Fill();
if b.err != nil { if b.err != nil {
return nn, b.err return nn, b.err
...@@ -108,6 +134,7 @@ func (b *BufRead) Read(p []byte) (nn int, err *os.Error) { ...@@ -108,6 +134,7 @@ func (b *BufRead) Read(p []byte) (nn int, err *os.Error) {
copySlice(p[0:n], b.buf[b.r:b.r+n]); copySlice(p[0:n], b.buf[b.r:b.r+n]);
p = p[n:len(p)]; p = p[n:len(p)];
b.r += n; b.r += n;
b.lastbyte = int(b.buf[b.r-1]);
nn += n nn += n
} }
return nn, nil return nn, nil
...@@ -127,6 +154,7 @@ func (b *BufRead) ReadByte() (c byte, err *os.Error) { ...@@ -127,6 +154,7 @@ func (b *BufRead) ReadByte() (c byte, err *os.Error) {
} }
c = b.buf[b.r]; c = b.buf[b.r];
b.r++; b.r++;
b.lastbyte = int(c);
return c, nil return c, nil
} }
...@@ -135,10 +163,18 @@ func (b *BufRead) UnreadByte() *os.Error { ...@@ -135,10 +163,18 @@ func (b *BufRead) UnreadByte() *os.Error {
if b.err != nil { if b.err != nil {
return b.err return b.err
} }
if b.r == b.w && b.lastbyte >= 0 {
b.w = 1;
b.r = 0;
b.buf[0] = byte(b.lastbyte);
b.lastbyte = -1;
return nil;
}
if b.r <= 0 { if b.r <= 0 {
return PhaseError return PhaseError
} }
b.r--; b.r--;
b.lastbyte = -1;
return nil return nil
} }
...@@ -163,6 +199,7 @@ func (b *BufRead) ReadRune() (rune int, size int, err *os.Error) { ...@@ -163,6 +199,7 @@ func (b *BufRead) ReadRune() (rune int, size int, err *os.Error) {
rune, size = utf8.DecodeRune(b.buf[b.r:b.w]); rune, size = utf8.DecodeRune(b.buf[b.r:b.w]);
} }
b.r += size; b.r += size;
b.lastbyte = int(b.buf[b.r-1]);
return rune, size, nil return rune, size, nil
} }
...@@ -343,8 +380,13 @@ func NewBufWriteSize(wr io.Write, size int) (b *BufWrite, err *os.Error) { ...@@ -343,8 +380,13 @@ func NewBufWriteSize(wr io.Write, size int) (b *BufWrite, err *os.Error) {
return b, nil return b, nil
} }
func NewBufWrite(wr io.Write) (b *BufWrite, err *os.Error) { func NewBufWrite(wr io.Write) *BufWrite {
return NewBufWriteSize(wr, defaultBufSize); b, err := NewBufWriteSize(wr, defaultBufSize);
if err != nil {
// cannot happen - defaultBufSize is valid size
panic("bufio: NewBufWrite: ", err.String());
}
return b;
} }
// Flush the output buffer. // Flush the output buffer.
...@@ -393,6 +435,17 @@ func (b *BufWrite) Write(p []byte) (nn int, err *os.Error) { ...@@ -393,6 +435,17 @@ func (b *BufWrite) Write(p []byte) (nn int, err *os.Error) {
} }
n = b.Available() n = b.Available()
} }
if b.Available() == 0 && len(p) >= len(b.buf) {
// Large write, empty buffer.
// Write directly from p to avoid copy.
n, b.err = b.wr.Write(p);
nn += n;
p = p[n:len(p)];
if b.err != nil {
break;
}
continue;
}
if n > len(p) { if n > len(p) {
n = len(p) n = len(p)
} }
...@@ -416,3 +469,14 @@ func (b *BufWrite) WriteByte(c byte) *os.Error { ...@@ -416,3 +469,14 @@ func (b *BufWrite) WriteByte(c byte) *os.Error {
return nil return nil
} }
// buffered input and output
type BufReadWrite struct {
*BufRead;
*BufWrite;
}
func NewBufReadWrite(r *BufRead, w *BufWrite) *BufReadWrite {
return &BufReadWrite{r, w}
}
...@@ -174,12 +174,12 @@ var bufsizes = []int { ...@@ -174,12 +174,12 @@ var bufsizes = []int {
} }
func TestBufReadSimple(t *testing.T) { func TestBufReadSimple(t *testing.T) {
b, e := NewBufRead(newByteReader(io.StringBytes("hello world"))); b := NewBufRead(newByteReader(io.StringBytes("hello world")));
if s := readBytes(b); s != "hello world" { if s := readBytes(b); s != "hello world" {
t.Errorf("simple hello world test failed: got %q", s); t.Errorf("simple hello world test failed: got %q", s);
} }
b, e = NewBufRead(newRot13Reader(newByteReader(io.StringBytes("hello world")))); b = NewBufRead(newRot13Reader(newByteReader(io.StringBytes("hello world"))));
if s := readBytes(b); s != "uryyb jbeyq" { if s := readBytes(b); s != "uryyb jbeyq" {
t.Error("rot13 hello world test failed: got %q", s); t.Error("rot13 hello world test failed: got %q", s);
} }
......
...@@ -16,12 +16,14 @@ import ( ...@@ -16,12 +16,14 @@ import (
"fmt"; "fmt";
"http"; "http";
"io"; "io";
"log";
"net"; "net";
"os"; "os";
"strconv"; "strconv";
) )
var ErrWriteAfterFlush = os.NewError("Conn.Write called after Flush") var ErrWriteAfterFlush = os.NewError("Conn.Write called after Flush")
var ErrHijacked = os.NewError("Conn has been hijacked")
type Conn struct type Conn struct
...@@ -32,132 +34,30 @@ type Handler interface { ...@@ -32,132 +34,30 @@ type Handler interface {
// Active HTTP connection (server side). // Active HTTP connection (server side).
type Conn struct { type Conn struct {
Fd io.ReadWriteClose; RemoteAddr string; // network address of remote side
RemoteAddr string; Req *Request; // current HTTP request
Req *Request;
Br *bufio.BufRead; fd io.ReadWriteClose; // i/o connection
buf *bufio.BufReadWrite; // buffered fd
br *bufio.BufRead; handler Handler; // request handler
bw *bufio.BufWrite; hijacked bool; // connection has been hijacked by handler
close bool;
chunking bool; // state for the current reply
flushed bool; closeAfterReply bool; // close connection after this reply
header map[string] string; chunking bool; // using chunked transfer encoding for reply body
wroteHeader bool; wroteHeader bool; // reply header has been written
handler Handler; header map[string] string; // reply header parameters
}
// HTTP response codes.
// TODO(rsc): Maybe move these to their own file, so that
// clients can use them too.
const (
StatusContinue = 100;
StatusSwitchingProtocols = 101;
StatusOK = 200;
StatusCreated = 201;
StatusAccepted = 202;
StatusNonAuthoritativeInfo = 203;
StatusNoContent = 204;
StatusResetContent = 205;
StatusPartialContent = 206;
StatusMultipleChoices = 300;
StatusMovedPermanently = 301;
StatusFound = 302;
StatusSeeOther = 303;
StatusNotModified = 304;
StatusUseProxy = 305;
StatusTemporaryRedirect = 307;
StatusBadRequest = 400;
StatusUnauthorized = 401;
StatusPaymentRequired = 402;
StatusForbidden = 403;
StatusNotFound = 404;
StatusMethodNotAllowed = 405;
StatusNotAcceptable = 406;
StatusProxyAuthRequired = 407;
StatusRequestTimeout = 408;
StatusConflict = 409;
StatusGone = 410;
StatusLengthRequired = 411;
StatusPreconditionFailed = 412;
StatusRequestEntityTooLarge = 413;
StatusRequestURITooLong = 414;
StatusUnsupportedMediaType = 415;
StatusRequestedRangeNotSatisfiable = 416;
StatusExpectationFailed = 417;
StatusInternalServerError = 500;
StatusNotImplemented = 501;
StatusBadGateway = 502;
StatusServiceUnavailable = 503;
StatusGatewayTimeout = 504;
StatusHTTPVersionNotSupported = 505;
)
var statusText = map[int]string {
StatusContinue: "Continue",
StatusSwitchingProtocols: "Switching Protocols",
StatusOK: "OK",
StatusCreated: "Created",
StatusAccepted: "Accepted",
StatusNonAuthoritativeInfo: "Non-Authoritative Information",
StatusNoContent: "No Content",
StatusResetContent: "Reset Content",
StatusPartialContent: "Partial Content",
StatusMultipleChoices: "Multiple Choices",
StatusMovedPermanently: "Moved Permanently",
StatusFound: "Found",
StatusSeeOther: "See Other",
StatusNotModified: "Not Modified",
StatusUseProxy: "Use Proxy",
StatusTemporaryRedirect: "Temporary Redirect",
StatusBadRequest: "Bad Request",
StatusUnauthorized: "Unauthorized",
StatusPaymentRequired: "Payment Required",
StatusForbidden: "Forbidden",
StatusNotFound: "Not Found",
StatusMethodNotAllowed: "Method Not Allowed",
StatusNotAcceptable: "Not Acceptable",
StatusProxyAuthRequired: "Proxy Authentication Required",
StatusRequestTimeout: "Request Timeout",
StatusConflict: "Conflict",
StatusGone: "Gone",
StatusLengthRequired: "Length Required",
StatusPreconditionFailed: "Precondition Failed",
StatusRequestEntityTooLarge: "Request Entity Too Large",
StatusRequestURITooLong: "Request URI Too Long",
StatusUnsupportedMediaType: "Unsupported Media Type",
StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
StatusExpectationFailed: "Expectation Failed",
StatusInternalServerError: "Internal Server Error",
StatusNotImplemented: "Not Implemented",
StatusBadGateway: "Bad Gateway",
StatusServiceUnavailable: "Service Unavailable",
StatusGatewayTimeout: "Gateway Timeout",
StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
} }
// Create new connection from rwc. // Create new connection from rwc.
func newConn(rwc io.ReadWriteClose, raddr string, handler Handler) (c *Conn, err *os.Error) { func newConn(rwc io.ReadWriteClose, raddr string, handler Handler) (c *Conn, err *os.Error) {
c = new(Conn); c = new(Conn);
c.Fd = rwc;
c.RemoteAddr = raddr; c.RemoteAddr = raddr;
c.handler = handler; c.handler = handler;
if c.br, err = bufio.NewBufRead(rwc.(io.Read)); err != nil { c.fd = rwc;
return nil, err br := bufio.NewBufRead(rwc);
} bw := bufio.NewBufWrite(rwc);
c.Br = c.br; c.buf = bufio.NewBufReadWrite(br, bw);
if c.bw, err = bufio.NewBufWrite(rwc); err != nil {
return nil, err
}
return c, nil return c, nil
} }
...@@ -165,14 +65,16 @@ func (c *Conn) SetHeader(hdr, val string) ...@@ -165,14 +65,16 @@ func (c *Conn) SetHeader(hdr, val string)
// Read next request from connection. // Read next request from connection.
func (c *Conn) readRequest() (req *Request, err *os.Error) { func (c *Conn) readRequest() (req *Request, err *os.Error) {
if req, err = ReadRequest(c.br); err != nil { if c.hijacked {
return nil, ErrHijacked
}
if req, err = ReadRequest(c.buf.BufRead); err != nil {
return nil, err return nil, err
} }
// Reset per-request connection state. // Reset per-request connection state.
c.header = make(map[string] string); c.header = make(map[string] string);
c.wroteHeader = false; c.wroteHeader = false;
c.flushed = false;
c.Req = req; c.Req = req;
// Default output is HTML encoded in UTF-8. // Default output is HTML encoded in UTF-8.
...@@ -190,7 +92,7 @@ func (c *Conn) readRequest() (req *Request, err *os.Error) { ...@@ -190,7 +92,7 @@ func (c *Conn) readRequest() (req *Request, err *os.Error) {
// a Content-Length: header in the response, // a Content-Length: header in the response,
// but everyone who expects persistent connections // but everyone who expects persistent connections
// does HTTP/1.1 now. // does HTTP/1.1 now.
c.close = true; c.closeAfterReply = true;
c.chunking = false; c.chunking = false;
} }
...@@ -203,8 +105,12 @@ func (c *Conn) SetHeader(hdr, val string) { ...@@ -203,8 +105,12 @@ func (c *Conn) SetHeader(hdr, val string) {
// Write header. // Write header.
func (c *Conn) WriteHeader(code int) { func (c *Conn) WriteHeader(code int) {
if c.hijacked {
log.Stderr("http: Conn.WriteHeader on hijacked connection");
return
}
if c.wroteHeader { if c.wroteHeader {
// TODO(rsc): log log.Stderr("http: multiple Conn.WriteHeader calls");
return return
} }
c.wroteHeader = true; c.wroteHeader = true;
...@@ -220,19 +126,20 @@ func (c *Conn) WriteHeader(code int) { ...@@ -220,19 +126,20 @@ func (c *Conn) WriteHeader(code int) {
if !ok { if !ok {
text = "status code " + codestring; text = "status code " + codestring;
} }
io.WriteString(c.bw, proto + " " + codestring + " " + text + "\r\n"); io.WriteString(c.buf, proto + " " + codestring + " " + text + "\r\n");
for k,v := range c.header { for k,v := range c.header {
io.WriteString(c.bw, k + ": " + v + "\r\n"); io.WriteString(c.buf, k + ": " + v + "\r\n");
} }
io.WriteString(c.bw, "\r\n"); io.WriteString(c.buf, "\r\n");
} }
// TODO(rsc): BUG in 6g: must return "nn int" not "n int" // TODO(rsc): BUG in 6g: must return "nn int" not "n int"
// so that the implicit struct assignment in // so that the implicit struct assignment in
// return c.bw.Write(data) works. oops // return c.buf.Write(data) works. oops
func (c *Conn) Write(data []byte) (nn int, err *os.Error) { func (c *Conn) Write(data []byte) (nn int, err *os.Error) {
if c.flushed { if c.hijacked {
return 0, ErrWriteAfterFlush log.Stderr("http: Conn.Write on hijacked connection");
return 0, ErrHijacked
} }
if !c.wroteHeader { if !c.wroteHeader {
c.WriteHeader(StatusOK); c.WriteHeader(StatusOK);
...@@ -242,38 +149,35 @@ func (c *Conn) Write(data []byte) (nn int, err *os.Error) { ...@@ -242,38 +149,35 @@ func (c *Conn) Write(data []byte) (nn int, err *os.Error) {
} }
// TODO(rsc): if chunking happened after the buffering, // TODO(rsc): if chunking happened after the buffering,
// then there would be fewer chunk headers // then there would be fewer chunk headers.
// On the other hand, it would make hijacking more difficult.
if c.chunking { if c.chunking {
fmt.Fprintf(c.bw, "%x\r\n", len(data)); // TODO(rsc): use strconv not fmt fmt.Fprintf(c.buf, "%x\r\n", len(data)); // TODO(rsc): use strconv not fmt
} }
return c.bw.Write(data); return c.buf.Write(data);
} }
func (c *Conn) Flush() { func (c *Conn) flush() {
if c.flushed {
return
}
if !c.wroteHeader { if !c.wroteHeader {
c.WriteHeader(StatusOK); c.WriteHeader(StatusOK);
} }
if c.chunking { if c.chunking {
io.WriteString(c.bw, "0\r\n"); io.WriteString(c.buf, "0\r\n");
// trailer key/value pairs, followed by blank line // trailer key/value pairs, followed by blank line
io.WriteString(c.bw, "\r\n"); io.WriteString(c.buf, "\r\n");
} }
c.bw.Flush(); c.buf.Flush();
c.flushed = true;
} }
// Close the connection. // Close the connection.
func (c *Conn) Close() { func (c *Conn) close() {
if c.bw != nil { if c.buf != nil {
c.bw.Flush(); c.buf.Flush();
c.bw = nil; c.buf = nil;
} }
if c.Fd != nil { if c.fd != nil {
c.Fd.Close(); c.fd.Close();
c.Fd = nil; c.fd = nil;
} }
} }
...@@ -288,18 +192,32 @@ func (c *Conn) serve() { ...@@ -288,18 +192,32 @@ func (c *Conn) serve() {
// Until the server replies to this request, it can't read another, // Until the server replies to this request, it can't read another,
// so we might as well run the handler in this thread. // so we might as well run the handler in this thread.
c.handler.ServeHTTP(c, req); c.handler.ServeHTTP(c, req);
if c.Fd == nil { if c.hijacked {
// Handler took over the connection.
return; return;
} }
if !c.flushed { c.flush();
c.Flush(); if c.closeAfterReply {
}
if c.close {
break; break;
} }
} }
c.Close(); c.close();
}
// Allow client to take over the connection.
// After a handler calls c.Hijack(), the HTTP server library
// will never touch the connection again.
// It is the caller's responsibility to manage and close
// the connection.
func (c *Conn) Hijack() (fd io.ReadWriteClose, buf *bufio.BufReadWrite, err *os.Error) {
if c.hijacked {
return nil, nil, ErrHijacked;
}
c.hijacked = true;
fd = c.fd;
buf = c.buf;
c.fd = nil;
c.buf = nil;
return;
} }
// Adapter: can use RequestFunction(f) as Handler // Adapter: can use RequestFunction(f) as Handler
......
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// HTTP status codes. See RFC 2616.
package http
const (
StatusContinue = 100;
StatusSwitchingProtocols = 101;
StatusOK = 200;
StatusCreated = 201;
StatusAccepted = 202;
StatusNonAuthoritativeInfo = 203;
StatusNoContent = 204;
StatusResetContent = 205;
StatusPartialContent = 206;
StatusMultipleChoices = 300;
StatusMovedPermanently = 301;
StatusFound = 302;
StatusSeeOther = 303;
StatusNotModified = 304;
StatusUseProxy = 305;
StatusTemporaryRedirect = 307;
StatusBadRequest = 400;
StatusUnauthorized = 401;
StatusPaymentRequired = 402;
StatusForbidden = 403;
StatusNotFound = 404;
StatusMethodNotAllowed = 405;
StatusNotAcceptable = 406;
StatusProxyAuthRequired = 407;
StatusRequestTimeout = 408;
StatusConflict = 409;
StatusGone = 410;
StatusLengthRequired = 411;
StatusPreconditionFailed = 412;
StatusRequestEntityTooLarge = 413;
StatusRequestURITooLong = 414;
StatusUnsupportedMediaType = 415;
StatusRequestedRangeNotSatisfiable = 416;
StatusExpectationFailed = 417;
StatusInternalServerError = 500;
StatusNotImplemented = 501;
StatusBadGateway = 502;
StatusServiceUnavailable = 503;
StatusGatewayTimeout = 504;
StatusHTTPVersionNotSupported = 505;
)
var statusText = map[int]string {
StatusContinue: "Continue",
StatusSwitchingProtocols: "Switching Protocols",
StatusOK: "OK",
StatusCreated: "Created",
StatusAccepted: "Accepted",
StatusNonAuthoritativeInfo: "Non-Authoritative Information",
StatusNoContent: "No Content",
StatusResetContent: "Reset Content",
StatusPartialContent: "Partial Content",
StatusMultipleChoices: "Multiple Choices",
StatusMovedPermanently: "Moved Permanently",
StatusFound: "Found",
StatusSeeOther: "See Other",
StatusNotModified: "Not Modified",
StatusUseProxy: "Use Proxy",
StatusTemporaryRedirect: "Temporary Redirect",
StatusBadRequest: "Bad Request",
StatusUnauthorized: "Unauthorized",
StatusPaymentRequired: "Payment Required",
StatusForbidden: "Forbidden",
StatusNotFound: "Not Found",
StatusMethodNotAllowed: "Method Not Allowed",
StatusNotAcceptable: "Not Acceptable",
StatusProxyAuthRequired: "Proxy Authentication Required",
StatusRequestTimeout: "Request Timeout",
StatusConflict: "Conflict",
StatusGone: "Gone",
StatusLengthRequired: "Length Required",
StatusPreconditionFailed: "Precondition Failed",
StatusRequestEntityTooLarge: "Request Entity Too Large",
StatusRequestURITooLong: "Request URI Too Long",
StatusUnsupportedMediaType: "Unsupported Media Type",
StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
StatusExpectationFailed: "Expectation Failed",
StatusInternalServerError: "Internal Server Error",
StatusNotImplemented: "Not Implemented",
StatusBadGateway: "Bad Gateway",
StatusServiceUnavailable: "Service Unavailable",
StatusGatewayTimeout: "Gateway Timeout",
StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
}
...@@ -30,6 +30,10 @@ type ReadWriteClose interface { ...@@ -30,6 +30,10 @@ type ReadWriteClose interface {
Close() *os.Error; Close() *os.Error;
} }
type Close interface {
Close() *os.Error;
}
func WriteString(w Write, s string) (n int, err *os.Error) { func WriteString(w Write, s string) (n int, err *os.Error) {
b := make([]byte, len(s)+1); b := make([]byte, len(s)+1);
if !syscall.StringToBytes(b, s) { if !syscall.StringToBytes(b, s) {
......
...@@ -51,10 +51,7 @@ func testLog(t *testing.T, flag int, prefix string, pattern string, useLogf bool ...@@ -51,10 +51,7 @@ func testLog(t *testing.T, flag int, prefix string, pattern string, useLogf bool
if err1 != nil { if err1 != nil {
t.Fatal("pipe", err1); t.Fatal("pipe", err1);
} }
buf, err2 := bufio.NewBufRead(fd0); buf := bufio.NewBufRead(fd0);
if err2 != nil {
t.Fatal("bufio.NewBufRead", err2);
}
l := NewLogger(fd1, nil, prefix, flag); l := NewLogger(fd1, nil, prefix, flag);
if useLogf { if useLogf {
l.Logf("hello %d world", 23); l.Logf("hello %d world", 23);
......
...@@ -18,10 +18,7 @@ func TestReadLine(t *testing.T) { ...@@ -18,10 +18,7 @@ func TestReadLine(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("open %s: %v", filename, err); t.Fatalf("open %s: %v", filename, err);
} }
br, err1 := bufio.NewBufRead(fd); br := bufio.NewBufRead(fd);
if err1 != nil {
t.Fatalf("bufio.NewBufRead: %v", err1);
}
file := _Open(filename); file := _Open(filename);
if file == nil { if file == nil {
......
...@@ -98,10 +98,7 @@ func TestFp(t *testing.T) { ...@@ -98,10 +98,7 @@ func TestFp(t *testing.T) {
panicln("testfp: open testfp.txt:", err.String()); panicln("testfp: open testfp.txt:", err.String());
} }
b, err1 := bufio.NewBufRead(fd); b := bufio.NewBufRead(fd);
if err1 != nil {
panicln("testfp NewBufRead:", err1.String());
}
lineno := 0; lineno := 0;
for { for {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment