golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.79k stars 17.64k forks source link

go/build: does not support examples that use cgo #20381

Open bcmills opened 7 years ago

bcmills commented 7 years ago

While prototyping a library to work around #13656, I ran in to a pretty major issue with the godoc tool. It doesn't generate any documentation at all for packages whose tests use cgo, which implies that packages designed for use with cgo cannot contain idiomatic examples.

bcmills:~/src$ go version
go version devel +b53acd89db Tue May 16 17:15:11 2017 +0000 linux/amd64
bcmills:~/src$ go tool doc cslice
doc: use of cgo in test /usr/local/google/home/bcmills/src/cslice/cslice_test.go not supported

cslice/cslice_test.go:

package cslice_test

/*
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
*/
import "C"

import (
    "fmt"
    "reflect"
    "testing"
    "unsafe"

    "cslice"
)

func ExampleSetAt() {
    var (
        original *C.char  = C.CString("Hello, world!")
        sz       C.size_t = C.strlen(original)
    )
    defer C.free(unsafe.Pointer(original))

    var alias []byte
    cslice.SetAt(&alias, unsafe.Pointer(original), int(sz))

    fmt.Println("original:", C.GoString(original))
    fmt.Println("alias:", string(alias))
    copy(alias, "Adios")
    fmt.Println("original:", C.GoString(original))
    fmt.Println("alias:", string(alias))

    // Output:
    // original: Hello, world!
    // alias: Hello, world!
    // original: Adios, world!
    // alias: Adios, world!
}

func ExampleReflectAt() {
    var (
        original *C.char  = C.CString("Hello, world!")
        sz       C.size_t = C.strlen(original)
    )
    defer C.free(unsafe.Pointer(original))

    alias := cslice.ReflectAt(reflect.TypeOf(byte(0)), unsafe.Pointer(original), int(sz))

    fmt.Println("original:", C.GoString(original))
    fmt.Println("alias:", string(alias.Bytes()))
    reflect.Copy(alias, reflect.ValueOf([]byte("Adios")))
    fmt.Println("original:", C.GoString(original))
    fmt.Println("alias:", string(alias.Bytes()))

    // Output:
    // original: Hello, world!
    // alias: Hello, world!
    // original: Adios, world!
    // alias: Adios, world!
}
bcmills commented 7 years ago

(CC: @alandonovan @ianlancetaylor)

ianlancetaylor commented 6 years ago

The error use of cgo in test ... not supported is not coming from go tool doc, it's coming from the go/build package.

dpifke commented 6 years ago

I did some preliminary investigation on this since I was curious. The commit that introduced the error message is from 2011, here:

https://go.googlesource.com/go/+/66bedf82210220e45ca2eb4919fe764e6a022630%5E%21/#F2

(the check now lives in src/go/build/build.go)

I tried removing the check. Go builds and all of the tests pass. Building an example with CGo still fails, however, due to being unable to import anything from the stdlib:

./cgo_test.go:10:2: can't find import: "fmt"

So it appears that there's more to this issue than just removing the check. Something about CGo confuses the importer when building tests.

dpifke commented 6 years ago

So the root problem, which is implied by the build.Package documentation, seems to be that a file is expected to appear in exactly one of GoFiles, CgoFiles, TestGoFiles, XTestGoFiles, or IgnoredGoFiles.

Adding support for Cgo in tests appears to involve adding TestCgoFiles and XTestCgoFiles to build.Package, and then teaching src/cmd/go/internal/load/test.go about these fields. I'm going to take a stab at this.