Go Cover command gives incorrect code coverage

This is first-time trying to experiment with code coverage for go-applications.

I am trying to play around with -cover flag to test code coverage but rest-endpoint.
I am referring this article: https://go.dev/doc/build-cover
Above example is working fine on machine - however I tried extending it to simple-rest api - it doesn’t seem to show 0% code coverage

This is smaller version prototype that I am trying to work.
I am seeing 0% code coverage even if the I am seeing the endpoint being hit, data getting retrieved

main.go

package main

import (
	"encoding/json"
	"log"
	"net/http"
)

type Item struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Price int    `json:"price"`
}

var inventory []Item

func main() {
	// Initialize inventory
	inventory = []Item{
		{ID: "1", Name: "Keyboard", Price: 50},
		{ID: "2", Name: "Mouse", Price: 20},
	}

	http.HandleFunc("/items", getItems)
	http.HandleFunc("/item/", getItemByID)

	log.Print("Starting server at port 8080")

	log.Fatal(http.ListenAndServe(":8080", nil))

}

func getItems(w http.ResponseWriter, r *http.Request) {
	// log the method
	log.Print("INVOKED > getItems ; URL : ", r.URL)

	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(inventory); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func getItemByID(w http.ResponseWriter, r *http.Request) {
	// log the method
	log.Print("INVOKED > getItemByID ; URL : ", r.URL)

	id := r.URL.Path[len("/item/"):]
	for _, item := range inventory {
		if item.ID == id {
			w.Header().Set("Content-Type", "application/json")
			if err := json.NewEncoder(w).Encode(item); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			return
		}
	}
	http.NotFound(w, r)
}

here’s how I trigger the rest-app

curl http://localhost:8080/items
curl http://localhost:8080/item/1
curl http://localhost:8080/item/2

here’s how I build binary

go build -cover -o myapp.exe main.go
GOCOVERDIR=coverData ./myapp.exe

I am seeing coverage files getting generated in cover folder - I noticed that here it is not generating the
covcounters file in my cover folder

$ ls coverData
covmeta.a9e2dc969ed2da98e736682fc77dd35f

here how I generate codeCoverage & reports

$ go tool covdata textfmt -i=coverData -o cov_func.txt
$ go tool cover -func=cov_func.txt
\my-go-rest-get\main.go:17:    main        0.0%
\my-go-rest-get\main.go:33:    getItems    0.0%
\my-go-rest-get\main.go:44:    getItemByID 0.0%
total:                                                                          (statements)
0.0%

Background:

My end-goal is to migrate this code-coverage to a larger micro services ecosystem with multiple go-services running & run our rest-integration suit on these go-services. We already abundant unit test coverage in our code-base greater than 90%.

Our plan is to get better insights about areas of improvement in rest-integration tests-suit.

Note: This is my first post, not fully sure if this issue was already discussed.

I was to fix the above issue by gracefully listening to exit signal from system.

It seems that SIGINT abruptly closes the app, resulting in missing code-coverage.
Here is updated logic - work in progress:
main.go

package main

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
)

type Item struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Price int    `json:"price"`
}

var inventory []Item

func main() {
	// Initialize inventory
	inventory = []Item{
		{ID: "1", Name: "Keyboard", Price: 50},
		{ID: "2", Name: "Mouse", Price: 20},
	}

	http.HandleFunc("/items", getItems)
	http.HandleFunc("/item/", getItemByID)

	server := &http.Server{
		Addr: ":8080",
	}

	// invoke backkgr handler to listen for exit signal & handler server-exit
	// make sure to correctly close server on exit signal
	go func() {
		// listen for close signal
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
		<-sigChan

		// close server
		if err := server.Close(); err != nil {
			log.Fatalf("HTTP close error: %v", err)
		}
	}()

	log.Print("Starting server at port 8080")
	if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
		log.Fatalf("HTTP server error: %v", err)
	}
}

func getItems(w http.ResponseWriter, r *http.Request) {
	// log the method
	log.Print("INVOKED > getItems ; URL : ", r.URL)

	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(inventory); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func getItemByID(w http.ResponseWriter, r *http.Request) {
	// log the method
	log.Print("INVOKED > getItemByID ; URL : ", r.URL)

	id := r.URL.Path[len("/item/"):]
	for _, item := range inventory {
		if item.ID == id {
			w.Header().Set("Content-Type", "application/json")
			if err := json.NewEncoder(w).Encode(item); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			return
		}
	}
	http.NotFound(w, r)
}

Here’s the output:

$ ./testCover.sh && sleep 20s && ./createReport.sh
{"id":"1","name":"Keyboard","price":50}
{"id":"2","name":"Mouse","price":20}
$ ./createReport.sh
my-go-rest-get\main.go:21:    main            84.6%
my-go-rest-get\main.go:52:    getItems        0.0%     
my-go-rest-get\main.go:63:    getItemByID     70.0%    
total:                		(statements)      64.3%