The Go Blog

Go fonts

16 November 2016

An Announcement

The experimental user interface toolkit being built at golang.org/x/exp/shiny includes several text elements, but there is a problem with testing them: What font should be used? Answering this question led us to today's announcement, the release of a family of high-quality WGL4 TrueType fonts, created by the Bigelow & Holmes type foundry specifically for the Go project.

The font family, called Go (naturally), includes proportional- and fixed-width faces in normal, bold, and italic renderings. The fonts have been tested for technical uses, particularly programming. Go source code looks particularly good when displayed in Go fonts, as its name implies, with things like punctuation characters easily distinguishable and operators lined up and placed consistently:

Perhaps the most remarkable feature of the Go fonts is their license: They are licensed under the same open source license as the rest of the Go project's software, an unusually free arrangement for a high-quality font set.

Here are samples of the proportionally-spaced...

and monospaced fonts:

How to use them

If you just want the TTF files, run

git clone https://go.googlesource.com/image

and copy them from the subsequent image/font/gofont/ttfs directory. If you want to use Go (the fonts) with Go (the software), each font is provided by a separate package. To use the Go Regular font in a program, import golang.org/x/image/font/gofont/goregular, and write:

font, err := truetype.Parse(goregular.TTF)

The github.com/golang/freetype/truetype package provides the truetype.Parse function today. There is also work underway to add a TrueType package under golang.org/x again licensed under the same open source license as the rest of the Go project's software.

We leave it to you to find some of the other unusual properties the fonts have, but for an overview of the fonts' design we asked Chuck Bigelow to provide some background. The remainder of this blog post is his response.

Notes on the fonts, by Chuck Bigelow

The Go fonts are divided into two sets, Go proportional, which is sans-serif, and Go Mono, which is slab-serif.

Go proportional fonts

Sans-serif

Go proportional fonts are sans-serif, like several popular fonts for screen displays. There is some evidence that some sans-serif faces at small sizes and low resolutions on screens are slightly more legible than their seriffed counterparts, while at large sizes, there is not a significant difference in legibility between sans and seriffed faces, at least in the pair tested. [1] (The bracketed numbers refer to the references listed at the end of this article.)

Style

Go sans-serif fonts are "humanist" rather than "grotesque" in style. This is an historical distinction, not an aesthetic judgment. Widely used sans-serif fonts like Helvetica and Arial are called grotesque because an early 19th century sans-serif typeface was named "Grotesque," and the name became generic.

The shapes of modern grotesque fonts like Helvetica are sculpted, with smooth, assimilated forms.

Humanist sans-serifs are derived from Humanist handwriting and early fonts of the Italian Renaissance and still show subtle traces of pen-written calligraphy. There is some evidence that humanist fonts are more legible than grotesque fonts. [2]

Italics

Go proportional italics have the same width metrics as the roman fonts. Go italics are oblique versions of the romans, with one noticeable exception: the italic lowercase 'a' is redesigned as a cursive single-story form to harmonize with the bowl shapes of the b d g p q set, in which the upright forms also adapt well to slanting, The addition of cursive 'a' makes the italics appear more lively than a simply slanted roman. Some typographers believe that slanted roman sans-serif italics are preferable to truly "cursive" sans Italics, in part because of history and design. [3]

The x-height

The x-height of a typeface is the height of the lowercase 'x' relative to the body size. The x-height of Go fonts is 53.0% of body size, a bit larger than the x-heights of Helvetica (52.3%) or Arial (51.9%), but the difference is usually unnoticeable at normal reading sizes. Typographers believe that larger x-heights contribute to greater legibility in small sizes and on screens. A study of "print size" (particularly x-height) and reading noted that types for reading on screens and for small sizes tend to have large x-heights. [4]

DIN Legibility Standard

The recent German DIN 1450 legibility standard recommends several features for font legibility, including differentiation of letter shapes to reduce confusion. The Go fonts conform to the 1450 standard by carefully differentiating zero from capital O; numeral 1 from capital I (eye) and lowercase l (ell); numeral 5 from capital S; and numeral 8 from capital B. The shapes of bowls of b d p q follow the natural asymmetries of legible Renaissance handwriting, aiding differentiation to reduce confusion. [5]

Weights

The Go proportional fonts come in three weights: Normal, Medium, and Bold. The Normal weight is strong enough that it maintains clarity on backlit screens, which often tend to erode letter features and thickness. The Medium weight has stem thickness 1.25 times the Normal, for greater sturdiness on bright screens or for users who prefer a sturdy font. The Bold weight has stem thickness 1.5 times the Normal, bold enough to be distinct from the normal weight. These Go fonts have CSS numerical weights of 400, 500, and 600. Although CSS specifies "Bold" as a 700 weight and 600 as Semibold or Demibold, the Go numerical weights match the actual progression of the ratios of stem thicknesses: Normal:Medium = 400:500; Normal:Bold = 400:600. The Bold weight name matches the use of “Bold” as the usual corresponding bold weight of a normal font. More discussion of the relationship of stem thicknesses, weight names, and CSS numbering is in [6].

WGL4 character set

The WGL4 character set, originally developed by Microsoft, is often used as an informal standard character set. WGL4 includes Western and Eastern European Latin characters plus Modern Greek and Cyrillic, with additional symbols, signs, and graphical characters, totalling more than 650 characters in all. The Go WGL4 fonts can be used to compose a wide range of languages. [7]

Metric compatibility with Arial and Helvetica

The Go sans-serif fonts are nearly metrically compatible with standard Helvetica or Arial characters. Texts set in Go occupy nearly the same space as texts in Helvetica or Arial (at the same size), but Go has a different look and texture because of its humanist style. Some Go letters with DIN legibility features are wider than corresponding letters in Helvetica or Arial, so some texts set in Go may take slightly more space.

Go Mono fonts

Monospaced

Go Mono fonts are monospaced—each letter has the same width as the other letters. Monospaced fonts have been used in programming since the beginning of computing and are still widely used because the typewriter regularity of their spacing makes text align in columns and rows, a style also found in Greek inscriptions of the 5th century BC. (The ancient Greeks didn't have typewriters or computer keyboards, but they did have great mathematicians and a great sense of symmetry and pattern that shaped their alphabet.)

Slab-serif

The Go Mono fonts have slab-shaped serifs, giving them a sturdy appearance.

Style

The underlying letter shapes of Go Mono are, like the Go sans-serif fonts, derived from humanist handwriting, but the monospacing and slab serifs tend to obscure the historical and stylistic connections.

Italics

Go Mono Italics are oblique versions of the romans, with the exception that the italic lowercase 'a' is redesigned as a cursive single-story form to harmonize with the bowl shapes of the b d g p q. The cursive 'a' makes the italics appear more lively than a simply slanted roman. As with many sans-serif fonts, it is believed that slanted roman slab-serifs fonts may be more legible than truly "cursive" italics.

The x-height

Go Mono fonts have the same x-height as Go sans-serif fonts, 53% of the body size. Go Mono looks almost 18% bigger than Courier, which has an x-height 45% of body size. Yet Go Mono has the same width as Courier, so the bigger look is gained with no loss of economy in characters per line.

DIN Legibility Standard

Go Mono fonts conform to the DIN 1450 standard by differentiating zero from capital O; numeral 1 from capital I (eye) and lowercase l (ell); numeral 5 from capital S; and numeral 8 from capital B. The shapes of bowls of b d p q follow the natural asymmetries of legible Renaissance handwriting, aiding differentiation and reducing confusion.

Weights

Go Mono fonts have two weights: Normal and Bold. The normal weight stem is the same as in Go Normal and thus maintains clarity on backlit screens, which tend to erode letter features and stem thickness. The bold stem thickness is 1.5 times thicker than the normal weight, hence the Bold Mono has the same stem thickness as Bold Go proportional. Because the letter width of monospaced bold is identical to the width of monospaced normal, the bold Mono appears slightly bolder than the proportional Go Bold, as more black pixels are put into the same area.)

Metric compatibility with popular monospaced fonts

Go Mono is metrically compatible with Courier and other monospaced fonts that match the "Pica" typewriter type widths of 10 characters per linear inch at 12 point. At 10 point, Go Mono fonts set 12 characters per inch. The TrueType fonts are scalable, of course, so Go Mono can be set at any size.

WGL4 character set

The Go Mono fonts offer the WGL4 character set often used as an informal standard character set. WGL4 includes Western and Eastern European Latin characters plus Modern Greek and Cyrillic, with additional symbols, signs, and graphical characters. The 650+ characters of the Go WGL4 sets can be used for a wide range of languages.

References

[1] Morris, R. A., Aquilante, K., Yager, D., & Bigelow, C. (2002, May). P‐13: Serifs Slow RSVP Reading at Very Small Sizes, but Don't Matter at Larger Sizes. In SID Symposium Digest of Technical Papers (Vol. 33, No. 1, pp. 244-247). Blackwell Publishing Ltd.

[2] Bryan Reimer et al. (2014) “Assessing the impact of typeface design in a text-rich automotive user interface”, Ergonomics, 57:11, 1643-1658. http://www.tandfonline.com/doi/abs/10.1080/00140139.2014.940000

[3] Adrian Frutiger - Typefaces: The Complete Works. H. Osterer and P. Stamm, editors. Birkhäuser, Basel, 2009, page 257.

[4] Legge, G. E., & Bigelow, C. A. (2011). Does print size matter for reading? A review of findings from vision science and typography. Journal of Vision, 11(5), 8-8. http://jov.arvojournals.org/article.aspx?articleid=2191906

[5] Charles Bigelow. "Oh, oh, zero!" TUGboat, Volume 34 (2013), No. 2. https://tug.org/TUGboat/tb34-2/tb107bigelow-zero.pdf https://tug.org/TUGboat/tb34-2/tb107bigelow-wang.pdf

[6] "Lucida Basic Font Weights" Bigelow & Holmes. http://lucidafonts.com/pages/facts

[7] WGL4 language coverage: Afrikaans, Albanian, Asu, Basque, Belarusian, Bemba, Bena, Bosnian, Bulgarian, Catalan, Chiga, Colognian, Cornish, Croatian, Czech, Danish, Embu, English, Esperanto, Estonian, Faroese, Filipino, Finnish, French, Friulian, Galician, Ganda, German, Greek, Gusii, Hungarian, Icelandic, Inari Sami, Indonesian, Irish, Italian, Jola-Fonyi, Kabuverdianu, Kalaallisut, Kalenjin, Kamba, Kikuyu, Kinyarwanda, Latvian, Lithuanian, Lower Sorbian, Luo, Luxembourgish, Luyia, Macedonian, Machame, Makhuwa-Meetto, Makonde, Malagasy, Malay, Maltese, Manx, Meru, Morisyen, North Ndebele, Northern Sami, Norwegian Bokmål, Norwegian Nynorsk, Nyankole, Oromo, Polish, Portuguese, Romanian, Romansh, Rombo, Rundi, Russian, Rwa, Samburu, Sango, Sangu, Scottish Gaelic, Sena, Serbian, Shambala, Shona, Slovak, Slovenian, Soga, Somali, Spanish, Swahili, Swedish, Swiss German, Taita, Teso, Turkish, Turkmen, Upper Sorbian, Vunjo, Walser, Welsh, Zulu

Jabberwocky in Go Regular

From en.wikipedia.org/wiki/Jabberwocky:

There is no Greek version listed. Instead, a pangram from clagnut.com/blog/2380/#Greek:

By Nigel Tao, Chuck Bigelow and Rob Pike

Seven years of Go

10 November 2016

Today marks seven years since we open-sourced our preliminary sketch of Go. With the help of the open source community, including more than a thousand individual contributors to the Go source repositories, Go has matured into a language used all over the world.

The most significant user-facing changes to Go over the past year are the addition of built-in support for HTTP/2 in Go 1.6 and the integration of the context package into the standard library in Go 1.7. But we’ve been making many less visible improvements. Go 1.7 changed the x86-64 compiler to use a new SSA-based back end, improving the performance of most Go programs by 10–20%. For Go 1.8, planned for release next February, we have changed the compilers for the other architectures to use the new back end too. We’ve also added new ports, to Android on 32-bit x86, Linux on 64-bit MIPS, and Linux on IBM z Systems. And we’ve developed new garbage-collection techniques that reduce typical “stop the world” pauses to under 100 microseconds. (Contrast that with Go 1.5’s big news of 10 milliseconds or less.)

This year kicked off with a global Go hackathon, the Gopher Gala, in January. Then there were Go conferences in India and Dubai in February, China and Japan in April, San Francisco in May, Denver in July, London in August, Paris last month, and Brazil this past weekend. And GothamGo in New York is next week. This year also saw more than 30 new Go user groups, eight new Women Who Go chapters, and four GoBridge workshops around the world.

We continue to be overwhelmed by and grateful for the enthusiasm and support of the Go community. Whether you participate by contributing changes, reporting bugs, sharing your expertise in design discussions, writing blog posts or books, running meetups, helping others learn or improve, open sourcing Go packages you wrote, or just being part of the Go community, the Go team thanks you for your help, your time, and your energy. Go would not be the success it is today without you.

Thank you, and here’s to another year of fun and success with Go!

By The Go Team

Introducing HTTP Tracing

4 October 2016

Introduction

In Go 1.7 we introduced HTTP tracing, a facility to gather fine-grained information throughout the lifecycle of an HTTP client request. Support for HTTP tracing is provided by the net/http/httptrace package. The collected information can be used for debugging latency issues, service monitoring, writing adaptive systems, and more.

HTTP events

The httptrace package provides a number of hooks to gather information during an HTTP round trip about a variety of events. These events include:

  • Connection creation
  • Connection reuse
  • DNS lookups
  • Writing the request to the wire
  • Reading the response

Tracing events

You can enable HTTP tracing by putting an *httptrace.ClientTrace containing hook functions into a request's context.Context. Various http.RoundTripper implementations report the internal events by looking for context's *httptrace.ClientTrace and calling the relevant hook functions.

The tracing is scoped to the request's context and users should put a *httptrace.ClientTrace to the request context before they start a request.

    req, _ := http.NewRequest("GET", "http://example.com", nil)
    trace := &httptrace.ClientTrace{
        DNSDone: func(dnsInfo httptrace.DNSDoneInfo) {
            fmt.Printf("DNS Info: %+v\n", dnsInfo)
        },
        GotConn: func(connInfo httptrace.GotConnInfo) {
            fmt.Printf("Got Conn: %+v\n", connInfo)
        },
    }
    req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
    if _, err := http.DefaultTransport.RoundTrip(req); err != nil {
        log.Fatal(err)
    }

During a round trip, http.DefaultTransport will invoke each hook as an event happens. The program above will print the DNS information as soon as the DNS lookup is complete. It will similarly print connection information when a connection is established to the request's host.

Tracing with http.Client

The tracing mechanism is designed to trace the events in the lifecycle of a single http.Transport.RoundTrip. However, a client may make multiple round trips to complete an HTTP request. For example, in the case of a URL redirection, the registered hooks will be called as many times as the client follows HTTP redirects, making multiple requests. Users are responsible for recognizing such events at the http.Client level. The program below identifies the current request by using an http.RoundTripper wrapper.

package main

import (
    "fmt"
    "log"
    "net/http"
    "net/http/httptrace"
)

// transport is an http.RoundTripper that keeps track of the in-flight
// request and implements hooks to report HTTP tracing events.
type transport struct {
    current *http.Request
}

// RoundTrip wraps http.DefaultTransport.RoundTrip to keep track
// of the current request.
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
    t.current = req
    return http.DefaultTransport.RoundTrip(req)
}

// GotConn prints whether the connection has been used previously
// for the current request.
func (t *transport) GotConn(info httptrace.GotConnInfo) {
    fmt.Printf("Connection reused for %v? %v\n", t.current.URL, info.Reused)
}

func main() {
    t := &transport{}

    req, _ := http.NewRequest("GET", "https://google.com", nil)
    trace := &httptrace.ClientTrace{
        GotConn: t.GotConn,
    }
    req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

    client := &http.Client{Transport: t}
    if _, err := client.Do(req); err != nil {
        log.Fatal(err)
    }
}

The program will follow the redirect of google.com to www.google.com and will output:

Connection reused for https://google.com? false
Connection reused for https://www.google.com/? false

The Transport in the net/http package supports tracing of both HTTP/1 and HTTP/2 requests.

If you are an author of a custom http.RoundTripper implementation, you can support tracing by checking the request context for an *httptest.ClientTrace and invoking the relevant hooks as the events occur.

Conclusion

HTTP tracing is a valuable addition to Go for those who are interested in debugging HTTP request latency and writing tools for network debugging for outbound traffic. By enabling this new facility, we hope to see HTTP debugging, benchmarking and visualization tools from the community — such as httpstat.

By Jaana Burcu Dogan

Using Subtests and Sub-benchmarks

3 October 2016

Introduction

In Go 1.7, the testing package introduces a Run method on the T and B types that allows for the creation of subtests and sub-benchmarks. The introduction of subtests and sub-benchmarks enables better handling of failures, fine-grained control of which tests to run from the command line, control of parallelism, and often results in simpler and more maintainable code.

Table-driven tests basics

Before digging into the details, let's first discuss a common way of writing tests in Go. A series of related checks can be implemented by looping over a slice of test cases:

func TestTime(t *testing.T) {
    testCases := []struct {
        gmt  string
        loc  string
        want string
    }{
        {"12:31", "Europe/Zuri", "13:31"},     // incorrect location name
        {"12:31", "America/New_York", "7:31"}, // should be 07:31
        {"08:08", "Australia/Sydney", "18:08"},
    }
    for _, tc := range testCases {
        loc, err := time.LoadLocation(tc.loc)
        if err != nil {
            t.Fatalf("could not load location %q", tc.loc)
        }
        gmt, _ := time.Parse("15:04", tc.gmt)
        if got := gmt.In(loc).Format("15:04"); got != tc.want {
            t.Errorf("In(%s, %s) = %s; want %s", tc.gmt, tc.loc, got, tc.want)
        }
    }
}

This approach, commonly referred to as table-driven tests, reduces the amount of repetitive code compared to repeating the same code for each test and makes it straightforward to add more test cases.

Table-driven benchmarks

Before Go 1.7 it was not possible to use the same table-driven approach for benchmarks. A benchmark tests the performance of an entire function, so iterating over benchmarks would just measure all of them as a single benchmark.

A common workaround was to define separate top-level benchmarks that each call a common function with different parameters. For instance, before 1.7 the strconv package's benchmarks for AppendFloat looked something like this:

func benchmarkAppendFloat(b *testing.B, f float64, fmt byte, prec, bitSize int) {
    dst := make([]byte, 30)
    b.ResetTimer() // Overkill here, but for illustrative purposes.
    for i := 0; i < b.N; i++ {
        AppendFloat(dst[:0], f, fmt, prec, bitSize)
    }
}

func BenchmarkAppendFloatDecimal(b *testing.B) { benchmarkAppendFloat(b, 33909, 'g', -1, 64) }
func BenchmarkAppendFloat(b *testing.B)        { benchmarkAppendFloat(b, 339.7784, 'g', -1, 64) }
func BenchmarkAppendFloatExp(b *testing.B)     { benchmarkAppendFloat(b, -5.09e75, 'g', -1, 64) }
func BenchmarkAppendFloatNegExp(b *testing.B)  { benchmarkAppendFloat(b, -5.11e-95, 'g', -1, 64) }
func BenchmarkAppendFloatBig(b *testing.B)     { benchmarkAppendFloat(b, 123456789123456789123456789, 'g', -1, 64) }
...

Using the Run method available in Go 1.7, the same set of benchmarks is now expressed as a single top-level benchmark:

func BenchmarkAppendFloat(b *testing.B) {
    benchmarks := []struct{
        name    string
        float   float64
        fmt     byte
        prec    int
        bitSize int
    }{
        {"Decimal", 33909, 'g', -1, 64},
        {"Float", 339.7784, 'g', -1, 64},
        {"Exp", -5.09e75, 'g', -1, 64},
        {"NegExp", -5.11e-95, 'g', -1, 64},
        {"Big", 123456789123456789123456789, 'g', -1, 64},
        ...
    }
    dst := make([]byte, 30)
    for _, bm := range benchmarks {
        b.Run(bm.name, func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                AppendFloat(dst[:0], bm.float, bm.fmt, bm.prec, bm.bitSize)
            }
        })
    }
}

Each invocation of the Run method creates a separate benchmark. An enclosing benchmark function that calls a Run method is only run once and is not measured.

The new code has more lines of code, but is more maintainable, more readable, and consistent with the table-driven approach commonly used for testing. Moreover, common setup code is now shared between runs while eliminating the need to reset the timer.

Table-driven tests using subtests

Go 1.7 also introduces a Run method for creating subtests. This test is a rewritten version of our earlier example using subtests:

func TestTime(t *testing.T) {
    testCases := []struct {
        gmt  string
        loc  string
        want string
    }{
        {"12:31", "Europe/Zuri", "13:31"},
        {"12:31", "America/New_York", "7:31"},
        {"08:08", "Australia/Sydney", "18:08"},
    }
    for _, tc := range testCases {
        t.Run(fmt.Sprintf("%s in %s", tc.gmt, tc.loc), func(t *testing.T) {
            loc, err := time.LoadLocation(tc.loc)
            if err != nil {
                t.Fatal("could not load location")
            }
            gmt, _ := time.Parse("15:04", tc.gmt)
            if got := gmt.In(loc).Format("15:04"); got != tc.want {
                t.Errorf("got %s; want %s", got, tc.want)
            }
        })
    }
}

The first thing to note is the difference in output from the two implementations. The original implementation prints:

--- FAIL: TestTime (0.00s)
    time_test.go:62: could not load location "Europe/Zuri"

Even though there are two errors, execution of the test halts on the call to Fatalf and the second test never runs.

The implementation using Run prints both:

--- FAIL: TestTime (0.00s)
    --- FAIL: TestTime/12:31_in_Europe/Zuri (0.00s)
        time_test.go:84: could not load location
    --- FAIL: TestTime/12:31_in_America/New_York (0.00s)
        time_test.go:88: got 07:31; want 7:31

Fatal and its siblings causes a subtest to be skipped but not its parent or subsequent subtests.

Another thing to note is the shorter error messages in the new implementation. Since the subtest name uniquely identifies the subtest there is no need to identify the test again within the error messages.

There are several other benefits to using subtests or sub-benchmarks, as clarified by the following sections.

Running specific tests or benchmarks

Both subtests and sub-benchmarks can be singled out on the command line using the -run or -bench flag. Both flags take a slash-separated list of regular expressions that match the corresponding parts of the full name of the subtest or sub-benchmark.

The full name of a subtest or sub-benchmark is a slash-separated list of its name and the names of all of its parents, starting with the top-level. The name is the corresponding function name for top-level tests and benchmarks, and the first argument to Run otherwise. To avoid display and parsing issues, a name is sanitized by replacing spaces with underscores and escaping non-printable characters. The same sanitizing is applied to the regular expressions passed to the -run or -bench flags.

A few examples:

Run tests that use a timezone in Europe:

$ go test -run=TestTime/"in Europe"
--- FAIL: TestTime (0.00s)
    --- FAIL: TestTime/12:31_in_Europe/Zuri (0.00s)
        time_test.go:85: could not load location

Run only tests for times after noon:

$ go test -run=Time/12:[0-9] -v
=== RUN   TestTime
=== RUN   TestTime/12:31_in_Europe/Zuri
=== RUN   TestTime/12:31_in_America/New_York
--- FAIL: TestTime (0.00s)
    --- FAIL: TestTime/12:31_in_Europe/Zuri (0.00s)
        time_test.go:85: could not load location
    --- FAIL: TestTime/12:31_in_America/New_York (0.00s)
        time_test.go:89: got 07:31; want 7:31

Perhaps a bit surprising, using -run=TestTime/New_York won't match any tests. This is because the slash present in the location names is treated as a separator as well. Instead use:

$ go test -run=Time//New_York
--- FAIL: TestTime (0.00s)
    --- FAIL: TestTime/12:31_in_America/New_York (0.00s)
        time_test.go:88: got 07:31; want 7:31

Note the // in the string passed to -run. The / in time zone name America/New_York is handled as if it were a separator resulting from a subtest. The first regular expression of the pattern (TestTime) matches the top-level test. The second regular expression (the empty string) matches anything, in this case the time and the continent part of the location. The third regular expression (New_York) matches the city part of the location.

Treating slashes in names as separators allows the user to refactor hierarchies of tests without the need to change the naming. It also simplifies the escaping rules. The user should escape slashes in names, for instance by replacing them with backslashes, if this poses a problem.

A unique sequence number is appended to test names that are not unique. So one could just pass an empty string to Run if there is no obvious naming scheme for subtests and the subtests can easily be identified by their sequence number.

Setup and Tear-down

Subtests and sub-benchmarks can be used to manage common setup and tear-down code:

func TestFoo(t *testing.T) {
    // <setup code>
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) {
        if !test(foo{B:1}) {
            t.Fail()
        }
    })
    // <tear-down code>
}

The setup and tear-down code will run if any of the enclosed subtests are run and will run at most once. This applies even if any of the subtests calls Skip, Fail, or Fatal.

Control of Parallelism

Subtests allow fine-grained control over parallelism. To understand how to use subtests in the way it is important to understand the semantics of parallel tests.

Each test is associated with a test function. A test is called a parallel test if its test function calls the Parallel method on its instance of testing.T. A parallel test never runs concurrently with a sequential test and its execution is suspended until its calling test function, that of the parent test, has returned. The -parallel flag defines the maximum number of parallel tests that can run in parallel.

A test blocks until its test function returns and all of its subtests have completed. This means that the parallel tests that are run by a sequential test will complete before any other consecutive sequential test is run.

This behavior is identical for tests created by Run and top-level tests. In fact, under the hood top-level tests are implemented as subtests of a hidden master test.

Run a group of tests in parallel

The above semantics allows for running a group of tests in parallel with each other but not with other parallel tests:

func TestGroupedParallel(t *testing.T) {
    for _, tc := range testCases {
        tc := tc // capture range variable
        t.Run(tc.Name, func(t *testing.T) {
            t.Parallel()
            if got := foo(tc.in); got != tc.out {
                t.Errorf("got %v; want %v", got, tc.out)
            }
            ...
        })
    }
}

The outer test will not complete until all parallel tests started by Run have completed. As a result, no other parallel tests can run in parallel to these parallel tests.

Note that we need to capture the range variable to ensure that tc gets bound to the correct instance.

Cleaning up after a group of parallel tests

In the previous example we used the semantics to wait on a group of parallel tests to complete before commencing other tests. The same technique can be used to clean up after a group of parallel tests that share common resources:

func TestTeardownParallel(t *testing.T) {
    // <setup code>
    // This Run will not return until its parallel subtests complete.
    t.Run("group", func(t *testing.T) {
        t.Run("Test1", parallelTest1)
        t.Run("Test2", parallelTest2)
        t.Run("Test3", parallelTest3)
    })
    // <tear-down code>
}

The behavior of waiting on a group of parallel tests is identical to that of the previous example.

Conclusion

Go 1.7's addition of subtests and sub-benchmarks allows you to write structured tests and benchmarks in a natural way that blends nicely into the existing tools. One way to think about this is that earlier versions of the testing package had a 1-level hierarchy: the package-level test was structured as a set of individual tests and benchmarks. Now that structure has been extended to those individual tests and benchmarks, recursively. In fact, in the implementation, the top-level tests and benchmarks are tracked as if they were subtests and sub-benchmarks of an implicit master test and benchmark: the treatment really is the same at all levels.

The ability for tests to define this structure enables fine-grained execution of specific test cases, shared setup and teardown, and better control over test parallelism. We are excited to see what other uses people find. Enjoy.

By Marcel van Lohuizen

Smaller Go 1.7 binaries

18 August 2016

Introduction

Go was designed for writing servers. That is how it is most widely used today, and as a result a lot of work on the runtime and compiler is focused on issues that matter to servers: latency, ease of deployment, precise garbage collection, fast startup time, performance.

As Go gets used for a wider variety of programs, there are new issues that must be considered. One of these is binary size. It has been on the radar for a long time (issue #6853 was filed over two years ago), but the growing interest in using Go for deploying binaries on smaller devices — such as the Raspberry Pi or mobile devices — means it received some attention for the Go 1.7 release.

Work done in Go 1.7

Three significant changes in Go 1.7 affect binary size.

The first is the new SSA backend that was enabled for AMD64 in this release. While the primary motivation for SSA was improved performance, the better generated code is also smaller. The SSA backend shrinks Go binaries by ~5%. We expect larger gains for the more RISC-like architectures like ARM and MIPS when those backends have been converted to SSA in Go 1.8.

The second change is method pruning. Until 1.6, all methods on all used types were kept, even if some of the methods were never called. This is because they might be called through an interface, or called dynamically using the reflect package. Now the compiler discards any unexported methods that do not match an interface. Similarly the linker can discard other exported methods, those that are only accessible through reflection, if the corresponding reflection features are not used anywhere in the program. That change shrinks binaries by 5–20%.

The third change is a more compact format for run-time type information used by the reflect package. The encoding format was originally designed to make the decoder in the runtime and reflect packages as simple as possible. By making this code a bit harder to read we can compress the format without affecting the run-time performance of Go programs. The new format shrinks Go binaries by a further 5–15%. Libraries built for Android and archives built for iOS shrink further as the new format contains fewer pointers, each of which requires dynamic relocations in position independent code.

In addition, there were many small improvements such as improved interface data layout, better static data layout, and simplified dependencies. For example, the HTTP client no longer links in the entire HTTP server. The full list of changes can be found in issue #6853.

Results

Typical programs, ranging from tiny toys to large production programs, are about 30% smaller when built with Go 1.7.

The canonical hello world program goes from 2.3MB to 1.6MB:

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

When compiled without debugging information the statically linked binary is now under a megabyte.

A large production program used for testing this cycle, jujud, went from 94MB to 67MB.

Position-independent binaries are 50% smaller.

In a position-independent executable (PIE), a pointer in a read-only data section requires a dynamic relocation. Because the new format for type information replaces pointers by section offsets, it saves 28 bytes per pointer.

Position-independent executables with debugging information removed are particularly important to mobile developers, as this is the kind of program shipped to phones. Big downloads make for a poor user experience, so the reduction here is good news.

Future Work

Several changes to the run-time type information were too late for the Go 1.7 freeze, but will hopefully make it into 1.8, further shrinking programs, especially position-independent ones.

These changes are all conservative, reducing binary size without increasing build time, startup time, overall execution time, or memory usage. We could take more radical steps to reduce binary size: the upx tool for compressing executables shrinks binaries by another 50% at the cost of increased startup time and potentially increased memory use. For extremely small systems (the kind that might live on a keychain) we could build a version of Go without reflection, though it is unclear whether such a restricted language would be sufficiently useful. For some algorithms in the runtime we could use slower but more compact implementions when every kilobyte counts. All of these call for more research in later development cycles.

To the many contributors who helped make Go 1.7 binaries smaller, thank you!

By David Crawshaw

See the index for more articles.