Použití Go pro automatizaci práce s aplikacemi s interaktivním příkazovým řádkem

26. 11. 2019
Doba čtení: 42 minut

Sdílet

 Autor: Go Lang
Jedním z nejužitečnějších nástrojů pro automatizaci i testování aplikací ovládaných z příkazové řádky je nástroj expect. Původní verze je vyvinuta v Tcl, ovšem existuje i mnoho knihoven určených pro jiné jazyky.

Obsah

1. Nástroje typu expect

2. Ukázky použití původního expect a varianty vytvořené v Pythonu

3. Balíčky go-expect, gexpect a GoExpect

4. Nejjednodušší použití balíčku go-expect – test obsahu standardního výstupu vybrané aplikace

5. Nastavení doby čekání při testu standardního výstupu aplikace

6. Podobný příklad s testem výstupu příkazu curl

7. Automatické ovládání aplikace díky koordinaci jejího standardního výstupu a vstupu

8. Programové ovládání interpretru jazyka Python

9. Reakce na několik alternativních řetězců na výstupu aplikace

10. Testování aplikací s textovým rozhraním kombinací balíčků testing a go-expect

11. Složitější příklad – test korektnosti výpočtů prováděných intepretrem Pythonu

12. Balíček gexpect

13. Přepis příkladu volajícího příkaz curl

14. Ovládání interaktivní hry spuštěné přes telnet

15. Ovládání interpretru Pythonu

16. Detekce, který interpret Pythonu byl spuštěn

17. Kombinace knihoven testing a gexpect

18. Test korektnosti výpočtů prováděných intepretrem Pythonu podruhé

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Nástroje typu expect

Při automatizaci úloh, v nichž je nutné kooperovat s interaktivními nástroji ovládanými ze standardního vstupu (telnet, ssh, ftp, všechny interpretry, gdb, různé instalační skripty atd. atd.), se mnohdy používá nástroj expect. S využitím tohoto nástroje je možné specifikovat a následně spouštět operace typu „pokud se na terminálu objeví text ‚login‘, pošli aplikaci na její standardní vstup obsah proměnné login“, popř. je dokonce možné provést rozeskoky podle toho, jaká zpráva se na terminálu objeví. I z tohoto důvodu se expect používá pro testování aplikací, například v telcom oblasti. Nástroj expect je skutečně všestranně použitelný, ovšem má také několik nevýhod. Jednou z nich je, že je naprogramován v dnes již poněkud obstarožním programovacím jazyce TCL a i skripty pro expect je tedy nutné v TCL vytvářet.

To může být pro současné programátory poněkud těžký oříšek a navíc je relativně složité zařadit expect například do integračních testů vyvinutých v odlišném programovacím jazyce. Dnes ovšem již existuje větší množství alternativních implementací prakticky stejné funkcionality, jakou nabízí samotný expect. Pro prakticky jakýkoli rozšířený programovací jazyk najdeme alespoň jednu alternativní implementaci. Týká se to pochopitelně i programovacího jazyka Go, pro který existuje knihoven hned několik. A právě těmito knihovnami – přesněji řečeno dvěma knihovnami z rozsáhlejší nabídky – se budeme v dnešní části seriálu o programovacím jazyce Go podrobněji zabývat.

2. Ukázky použití původního expect a varianty vytvořené v Pythonu

Podívejme se však nejprve na typický příklad použití nástroje expect. Tento příklad najdete například na Wikipedii, ale i v mnoha dalších článcích, které se tímto užitečným nástrojem zabývají. Ve skriptu je provedeno připojení (přes telnet) na vzdálený stroj. Ve chvíli, kdy vzdálený stroj čeká na zadání uživatelského jména, je mu posláno jméno (resp. přesněji řečeno jakýkoli text) uložené v proměnné my_user_id. Dále se na výzvu pro zadání hesla naprosto stejným způsobem předá heslo z proměnné nazvané my_password. Nakonec se očekává zobrazení výzvy (zde ve tvaru %); v této chvíli se na vzdálený stroj pošle specifikovaný příkaz a spojení se ukončí příkazem „exit“:

spawn telnet $remote_server
expect "username:"
# Send the username, and then wait for a password prompt.
send "$my_user_id\r"
expect "password:"
# Send the password, and then wait for a shell prompt.
send "$my_password\r"
expect "%"
# Send the prebuilt command, and then wait for another shell prompt.
send "$my_command\r"
expect "%"
# Capture the results of the command into a variable. This can be displayed, or written to disk.
set results $expect_out(buffer)
# Exit the telnet session, and wait for a special end-of-file character.
send "exit\r"
expect eof

Nepatrně složitější příklad (taktéž ovšem velmi typický – najdete ho v prakticky každém tutoriálu o expectu) se pokusí připojit na vzdálený stroj přes ssh. Tentokrát je ovšem proveden rozeskok na základě toho, jaká informace se vypíše. Při prvním připojení se totiž ssh zeptá, zda se skutečně připojuje k ověřenému stroji (odpovídáme zde automaticky „yes“, což ovšem není příliš bezpečné), při dalším připojení je již adresa zapamatována, takže se ssh přímo zeptá na heslo. Pokud nedojde ani k jedné variantě, je připojení ihned ukončeno příkazem exit:

set timeout 60
spawn ssh $user@machine
while {1} {
  expect {
    eof                          {break}
    "The authenticity of host"   {send "yes\r"}
    "password:"                  {send "$password\r"}
    "*\]"                        {send "exit\r"}
  }
}
wait
close $spawn_id
Poznámka: samotný příkaz expect v předchozím skriptu umožňuje rozeskok. V programovacím jazyce TCL je totiž velmi snadné přidávat další jazykové konstrukce, protože celé TCL je (poněkud zjednodušeně řečeno) postaveno pouze na několika textových substitucích a nikoli na pevně zadané syntaxi.

Pro zajímavost se ještě podívejme na způsob implementace funkcionality nástroje expect v Pythonu, konkrétně s využitím balíčku nazvaného pexpect. Tentokrát se spustí interpret Pythonu, na jeho výzvu (prompt) se zadá příkaz pro otočení řetězce a z výstupu se zjistí výsledek této operace:

import pexpect
 
c = pexpect.spawnu('/usr/bin/env python')
 
c.expect('>>>')
print('And now for something completely different...')
print(''.join(reversed((c.before))))
print('Yes, it\'s python, but it\'s backwards.')
print()
print('Escape character is \'^]\'.')
print(c.after, end=' ')
c.interact()
c.kill(1)
print('is alive:', c.isalive())

3. Balíčky go-expect, gexpect a GoExpect

Jak jsme si již řekli v úvodní kapitole, existuje pro programovací jazyk Go hned několik knihoven, které ve větší či menší míře implementují základní operace, které známe z původního nástroje expect. Jedná se o následující knihovny:

  1. Balíček goexpect
    https://github.com/google/goexpect
  2. Balíček go-expect
    https://github.com/Netflix/go-expect
  3. Balíček gexpect
    https://github.com/Thomas­Rooney/gexpect

V navazujících kapitolách se budeme zabývat především druhým zmíněným balíčkem pojmenovaným go-expect a balíčkem třetím gexpect. První balíček bude popsán v samostatném článku, protože se nabízí poměrně rozsáhlou funkcionalitu.

4. Nejjednodušší použití balíčku go-expect – test obsahu standardního výstupu vybrané aplikace

Prvním balíčkem s implementací vybraných operací nástroje expect v programovacím jazyce Go je balíček nazvaný go-expect. Ukažme si nyní jedno z nejjednodušších použití tohoto balíčku. Vytvoříme test, v němž spustíme příkaz uname (bez dalších parametrů) a následně otestujeme, zda se na standardním výstupu z tohoto nástroje objevil text „Linux“. Nejprve je nutné získat instanci virtuální konzole, pochopitelně s kontrolou, zda při její konstrukci nedošlo k chybě:

console, err := expect.NewConsole(expect.WithStdout(os.Stdout))
if err != nil {
        log.Fatal(err)
}
defer console.Close()

Dále se přes standardní knihovnu os/exec spustí příkaz „uname“ a upraví se jeho vstupně/výstupní proudy, aby příkaz bylo možné ovládat z virtuální konzole:

command := exec.Command("uname")
command.Stdin = console.Tty()
command.Stdout = console.Tty()
command.Stderr = console.Tty()
err = command.Start()

Na standardním výstupu aplikace by se měl objevit řetězec „Linux“, což ihned zjistíme:

console.ExpectString("Linux")

Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-root/blob/master/article43/go-expect/01_check_uname.go:

package main
 
import (
        "log"
        "os"
        "os/exec"
        "time"
 
        expect "github.com/Netflix/go-expect"
)
 
func main() {
        console, err := expect.NewConsole(expect.WithStdout(os.Stdout))
        if err != nil {
                log.Fatal(err)
        }
        defer console.Close()
 
        command := exec.Command("uname")
        command.Stdin = console.Tty()
        command.Stdout = console.Tty()
        command.Stderr = console.Tty()
 
        err = command.Start()
        if err != nil {
                log.Fatal(err)
        }
 
        time.Sleep(time.Second)
        console.ExpectString("Linux")
 
        err = command.Wait()
        if err != nil {
                log.Fatal(err)
        }
}

Při spuštění aplikace (psané v Go) se na standardním výstupu postupně objevují i texty vypsané spuštěným nástrojem uname:

Linux

5. Nastavení doby čekání při testu standardního výstupu aplikace

V případě, že se na standardním výstupu spuštěné aplikace neobjeví očekávaný řetězec (v předchozím příkladu to byl text „Linux“), bude program ve výchozím nastavení pozastaven na neomezenou dobu, protože nemůže vědět, kdy (a zda vůbec) se tento řetězec může objevit. Toto chování většinou není ideální, protože se může stát, že se očekávaný řetězec neukáže nikdy. Z tohoto důvodu lze již při inicializaci konzole určit čas, po který se má po zavolání metody Expect na řetězec čekat:

console, err := expect.NewConsole(expect.WithStdout(os.Stdout), expect.WithDefaultTimeout(100*time.Millisecond))

Příklad nepatrně upravíme takovým způsobem, aby se očekával řetězec „BSD“ namísto „Linux“:

str, err := console.ExpectString("BSD")
if err != nil {
        log.Fatalf("BSD expected, but got %s", str)
}

Takový řetězec se na testovaném stroji nemůže objevit, proto dojde po spuštění aplikace k očekávané chybě:

Linux
2019/11/23 13:01:37 BSD expected, but got Linux
exit status 1

Úplný kód druhého demonstračního příkladu najdeme na adrese https://github.com/tisnik/go-root/blob/master/article43/go-expect/02_check_uname_timeout.go:

package main
 
import (
        "log"
        "os"
        "os/exec"
        "time"
 
        expect "github.com/Netflix/go-expect"
)
 
func main() {
        console, err := expect.NewConsole(expect.WithStdout(os.Stdout), expect.WithDefaultTimeout(100*time.Millisecond))
        if err != nil {
                log.Fatal(err)
        }
        defer console.Close()
 
        command := exec.Command("uname")
        command.Stdin = console.Tty()
        command.Stdout = console.Tty()
        command.Stderr = console.Tty()
 
        err = command.Start()
        if err != nil {
                log.Fatal(err)
        }
 
        time.Sleep(time.Second)
        str, err := console.ExpectString("BSD")
        if err != nil {
                log.Fatalf("BSD expected, but got %s", str)
        }
 
        err = command.Wait()
        if err != nil {
                log.Fatal(err)
        }
}

6. Podobný příklad s testem výstupu příkazu curl

Přesné chování metody console.ExpectString si ověříme na dalším demonstračním příkladu, v němž bude spuštěn příkaz curl a testovat budeme, zda odpověď obsahuje informaci o přesunu stránky na odlišnou adresu (301 Moved Permanently):

package main
 
import (
        "log"
        "os"
        "os/exec"
        "time"
 
        expect "github.com/Netflix/go-expect"
)
 
func main() {
        console, err := expect.NewConsole(expect.WithStdout(os.Stdout))
        if err != nil {
                log.Fatal(err)
        }
        defer console.Close()
 
        command := exec.Command("curl", "-X", "HEAD", "-v", "github.com")
        command.Stdin = console.Tty()
        command.Stdout = console.Tty()
        command.Stderr = console.Tty()
 
        err = command.Start()
        if err != nil {
                log.Fatal(err)
        }
 
        time.Sleep(time.Second)
        console.ExpectString("Location: https://github.com/")
 
        err = command.Wait()
        if err != nil {
                log.Fatal(err)
        }
}

Po spuštění příkladu si povšimněte, že se vypíše pouze výstup až do očekávaného řetězce, ale další zprávy již nejsou ani vypsány ani zpracovány. Metoda console.ExpectString totiž prochází přečteným výstupem aplikace od aktuálního bodu až do toho okamžiku, kdy řetězec nalezne (pokud ho nenalezne, bude čekat na další výstup, popř. po určeném timeoutu skončí s chybou):

* Rebuilt URL to: github.com/
* Hostname was NOT found in DNS cache
*   Trying 140.82.118.4...
* Connected to github.com (140.82.118.4) port 80 (#0)
> HEAD / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: github.com
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Content-length: 0
< Location: https://github.com/

7. Automatické ovládání aplikace díky koordinaci jejího standardního výstupu a vstupu

Největší přednost nástrojů typu expect spočívá v jejich schopnosti ovládat jinou aplikaci díky koordinaci jejího standardního výstupu (tisknutých zpráv, například otázek) a vstupu. Ukážeme si to na jednoduchém příkladu – přes telnet se přihlásíme do hry Zombie MUD (MUD=Multi-User Dungeon), počkáme na zobrazení hlavního menu a pokud se menu skutečně zobrazí, použijeme příkaz D (Disconnect). Naprosto stejným způsobem by ovšem bylo možné naskriptovat vytvoření nové postavy či dokonce projití několika patry podzemí:

package main
 
import (
        "log"
        "os"
        "os/exec"
        "time"
 
        expect "github.com/Netflix/go-expect"
)
 
func main() {
        console, err := expect.NewConsole(expect.WithStdout(os.Stdout))
        if err != nil {
                log.Fatal(err)
        }
        defer console.Close()
 
        command := exec.Command("telnet", "zombiemud.org")
        command.Stdin = console.Tty()
        command.Stdout = console.Tty()
        command.Stderr = console.Tty()
 
        err = command.Start()
        if err != nil {
                log.Fatal(err)
        }
 
        time.Sleep(time.Second)
        console.ExpectString("... online since 1994")
        console.ExpectString("Your choice or name:")
        console.Send("d\n")
        console.ExpectString("Ok, see you later!")
 
        err = command.Wait()
        if err != nil {
                log.Fatal(err)
        }
}

Ukázka komunikace námi vytvořeného pomocného prográmku se hrou:

Trying 85.23.110.31...
Connected to zombiemud.org.
Escape character is '^]'.
      Welcome to ...
          ___                       __     __) __     __) ______
         (,   )          /)  ,     (, /|  /|  (, /   /   (, /    )
             / ______   (/_     _    / | / |    /   /      /    /
           _/_(_) // (_/_) _(__(/_) /  |/  |_  /   /     _/___ /_
       )   /                     (_/   '      (___(_   (_/___ /
      (__ /
                 ... online since 1994.
 
      There are currently 45 mortals and 5 wizards online.
 
            Give me your name or choose one of the following:
 
            [C]reate a new character     [W]ho is playing
            [V]isit the game             [S]tatus of the game
            [D]isconnect
 
            Your choice or name: d
Ok, see you later!

8. Programové ovládání interpretru jazyka Python

Podobným způsobem můžeme ovládat i interpretry různých programovacích jazyků. Další demonstrační příklad ukazuje ovládání interpretru Pythonu spouštěného příkazem python (na základě nastavení systému se tedy může jednat buď o Python 2 nebo o Python 3). Po spuštění interpretru očekáváme jeho výzvu (prompt), po jejímž objevení zadáme aritmetický výraz s očekáváním správného výsledku. Nakonec je interpret ukončen příkazem (přesněji řečeno funkcí) quit():

package main
 
import (
        "log"
        "os"
        "os/exec"
        "time"
 
        expect "github.com/Netflix/go-expect"
)
 
func main() {
        console, err := expect.NewConsole(expect.WithStdout(os.Stdout))
        if err != nil {
                log.Fatal(err)
        }
        defer console.Close()
 
        command := exec.Command("python")
        command.Stdin = console.Tty()
        command.Stdout = console.Tty()
        command.Stderr = console.Tty()
 
        err = command.Start()
        if err != nil {
                log.Fatal(err)
        }
 
        time.Sleep(time.Second)
        console.ExpectString(">>> ")
 
        console.SendLine("1+2")
        console.ExpectString("3")
        console.ExpectString(">>> ")
 
        console.SendLine("6*7")
        console.ExpectString("42")
        console.ExpectString(">>> ")
 
        console.SendLine("quit()")
 
        err = command.Wait()
        if err != nil {
                log.Fatal(err)
        }
}

Ukázka dialogu mezi naším prográmkem a interpretrem Pythonu (schválně nastaveným na starší verzi):

Python 2.7.6 (default, Nov 23 2017, 15:49:48)
[GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 1+2
3
>>> 6*7
42
>>>

9. Reakce na několik alternativních řetězců na výstupu aplikace

Poněkud komplikovanější situace nastane ve chvíli, kdy je nutné rozhodnout, který z řetězců se objevil na konzoli ovládané či testované aplikace. Máme dvě možnosti – buď použít regulární výrazy následované rozeskokem, nebo metodu console.ExpectString nahradit její obecnější variantou s více alternativními řetězci:

str, err := console.Expect(expect.String("Python 2", "Python 3"), expect.WithTimeout(100*time.Millisecond))

Následně je již možné zjistit, jaká situace nastala – zda se objevil alespoň jeden z řetězců či naopak řetězec žádný (což je chyba):

if err != nil {
        fmt.Println("Python not detected")
        log.Fatal(err)
}
if str == "Python 2" {
        console.SendLine("print 1,2,3")
        _, err = console.ExpectString("1 2 3")
        if err != nil {
                log.Fatal(err)
        }
        console.ExpectString(">>> ")
} else {
        console.SendLine("print(1,2,3)")
        _, err = console.ExpectString("1 2 3")
        if err != nil {
                log.Fatal(err)
        }
        console.ExpectString(">>> ")
}

Ukázka možného výstupu:

Python 2.7.6 (default, Nov 23 2017, 15:49:48)
[GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print 1,2,3
1 2 3
>>>
Poznámka: můžete si vyzkoušet nahradit příkaz python za python3.

Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:

package main
 
import (
        "fmt"
        "log"
        "os"
        "os/exec"
        "time"
 
        expect "github.com/Netflix/go-expect"
)
 
func main() {
        console, err := expect.NewConsole(expect.WithStdout(os.Stdout))
        if err != nil {
                log.Fatal(err)
        }
        defer console.Close()
 
        command := exec.Command("python")
        command.Stdin = console.Tty()
        command.Stdout = console.Tty()
        command.Stderr = console.Tty()
 
        err = command.Start()
        if err != nil {
                log.Fatal(err)
        }
 
        time.Sleep(time.Second)
 
        str, err := console.Expect(expect.String("Python 2", "Python 3"), expect.WithTimeout(100*time.Millisecond))
        if err != nil {
                fmt.Println("Python not detected")
                log.Fatal(err)
        }
        if str == "Python 2" {
                console.SendLine("print 1,2,3")
                _, err = console.ExpectString("1 2 3")
                if err != nil {
                        log.Fatal(err)
                }
                console.ExpectString(">>> ")
        } else {
                console.SendLine("print(1,2,3)")
                _, err = console.ExpectString("1 2 3")
                if err != nil {
                        log.Fatal(err)
                }
                console.ExpectString(">>> ")
        }
 
        console.SendLine("quit()")
 
        err = command.Wait()
        if err != nil {
                log.Fatal(err)
        }
}

10. Testování aplikací s textovým rozhraním kombinací balíčků testing a go-expect

Nic nám nebrání použít knihovnu go-expect společně s knihovnou testing pro vytvoření testů funkcionality či integračních testů. Jedna z možností (pravda – poněkud umělá) je ukázána v dalším příkladu, v němž testujeme schopnost interpretru Pythonu vyhodnotit parametry příkazu print (Python 2) či funkce print (Python 3). Celý test je psán s využitím možností nabízených standardní knihovnou testing:

package main
 
import (
        "os"
        "os/exec"
        "testing"
        "time"
 
        expect "github.com/Netflix/go-expect"
)
 
func TestPythonInterpreter(t *testing.T) {
        console, err := expect.NewConsole(expect.WithStdout(os.Stdout))
        if err != nil {
                t.Fatal(err)
        }
        defer console.Close()
        t.Log("Console created")
 
        command := exec.Command("python")
        command.Stdin = console.Tty()
        command.Stdout = console.Tty()
        command.Stderr = console.Tty()
 
        err = command.Start()
        if err != nil {
                t.Fatal(err)
        }
 
        t.Log("Python interpreter started")
        time.Sleep(time.Second)
 
        str, err := console.Expect(expect.String("Python 2", "Python 3"), expect.WithTimeout(100*time.Millisecond))
        if err != nil {
                t.Fatal("Python not detected")
        }
        t.Log("Python interpreter detected: " + str)
 
        if str == "Python 2" {
                console.SendLine("print 1,2,3")
                _, err = console.ExpectString("1 2 3")
                if err != nil {
                        t.Fatal("print statement failure")
                }
                t.Log("print statement works as expected")
                _, err = console.ExpectString(">>> ")
                if err != nil {
                        t.Fatal("prompt is not displayed")
                }
        } else {
                console.SendLine("print(1,2,3)")
                _, err = console.ExpectString("1 2 3")
                if err != nil {
                        t.Fatal("print function failure")
                }
                t.Log("print function works as expected")
                _, err = console.ExpectString(">>> ")
                if err != nil {
                        t.Fatal("prompt is not displayed")
                }
        }
 
        console.SendLine("quit()")
 
        err = command.Wait()
        if err != nil {
                t.Fatal(err)
        }
        t.Log("Done")
}

Test musíme spustit příkazem go test -v jméno_souboru.go. Výstup může vypadat následovně:

=== RUN   TestPythonInterpreter
Python 2.7.6 (default, Nov 23 2017, 15:49:48)
[GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print 1,2,3
1 2 3
>>>
--- PASS: TestPythonInterpreter (1.01s)
    07_python_test.go:18: Console created
    07_python_test.go:30: Python interpreter started
    07_python_test.go:37: Python interpreter detected: Python 2
    07_python_test.go:45: print statement works as expected
    07_python_test.go:69: Done
PASS
ok      command-line-arguments  1.011s
Poznámka: pochopitelně je možné (spíše více než pravděpodobné), že konkrétní verze Pythonu se bude na vašem počítači odlišovat. Ovšem testy by měly proběhnout bez chyby proti jakémukoli interpretru.

11. Složitější příklad – test korektnosti výpočtů prováděných intepretrem Pythonu

Posledním příkladem, v němž využijeme knihovnu go-expect bude test korektnosti výpočtů s využitím operátoru ** v Pythonu. Tento operátor slouží pro výpočet funkce xy; pro jednoduchost otestujeme jeho funkcionalitu při výpočtu mocninné řady o základu 2 (1, 2, 4, 8, 16, …). Naivní, ovšem funkční implementace založená na použití knihoven testing a go-expect může vypadat následovně:

package main
 
import (
        "fmt"
        "os"
        "os/exec"
        "testing"
        "time"
 
        expect "github.com/Netflix/go-expect"
)
 
func TestPythonInterpreter(t *testing.T) {
        console, err := expect.NewConsole(expect.WithStdout(os.Stdout))
        if err != nil {
                t.Fatal(err)
        }
        defer console.Close()
        t.Log("Console created")
 
        command := exec.Command("python")
        command.Stdin = console.Tty()
        command.Stdout = console.Tty()
        command.Stderr = console.Tty()
 
        err = command.Start()
        if err != nil {
                t.Fatal(err)
        }
 
        t.Log("Python interpreter started")
        time.Sleep(time.Second)
 
        str, err := console.Expect(expect.String("Python 2", "Python 3"), expect.WithTimeout(100*time.Millisecond))
        if err != nil {
                t.Fatal("Python not detected")
        }
        t.Log("Python interpreter detected: " + str)
 
        for i := uint(1); i < 10; i++ {
                console.SendLine(fmt.Sprintf("2**%d", i))
                _, err = console.Expectf("%d", 1<<i)
                if err != nil {
                        t.Fatal("Math is wrong!")
                }
                t.Logf("Math is ok for input %d", i)
        }
 
        console.SendLine("quit()")
 
        err = command.Wait()
        if err != nil {
                t.Fatal(err)
        }
        t.Log("Done")
}

Tento test spustíme příkazem:

$ go test -v 08_python_math_test.go

S následujícími výsledky (platí pro výchozí interpret Pythonu na testovacím počítači):

=== RUN   TestPythonInterpreter
Python 2.7.6 (default, Nov 23 2017, 15:49:48)
[GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 2**1
2**2
2
>>> 2**2
4
>>> 2**3
8
>>> 2**4
16
>>> 2**5
32
>>> 2**6
64
>>> 2**7
128
>>> 2**8
256
>>> 2**9
512
--- PASS: TestPythonInterpreter (1.01s)
    08_python_math_test.go:19: Console created
    08_python_math_test.go:31: Python interpreter started
    08_python_math_test.go:38: Python interpreter detected: Python 2
    08_python_math_test.go:46: Math is ok for input 1
    08_python_math_test.go:46: Math is ok for input 2
    08_python_math_test.go:46: Math is ok for input 3
    08_python_math_test.go:46: Math is ok for input 4
    08_python_math_test.go:46: Math is ok for input 5
    08_python_math_test.go:46: Math is ok for input 6
    08_python_math_test.go:46: Math is ok for input 7
    08_python_math_test.go:46: Math is ok for input 8
    08_python_math_test.go:46: Math is ok for input 9
    08_python_math_test.go:55: Done
PASS
ok      command-line-arguments  1.013s

12. Balíček gexpect

Druhým balíčkem, s nímž se dnes ve stručnosti seznámíme, je balíček nazvaný gexpect. Jeho použití je ve skutečnosti pro jednodušší případy mnohem snadnější, než tomu bylo u balíčku go-expect. Ostatně se podívejme na to, jak lze přepsat příklad spouštějící nástroj uname a testující jeho výsledek:

package main
 
import (
        "log"

        "github.com/ThomasRooney/gexpect"
)
 
func main() {
        child, err := gexpect.Spawn("uname")
        if err != nil {
                log.Fatal(err)
        }
        err = child.Expect("Linux")
        if err != nil {
                log.Fatal(err)
        }
        child.Wait()
}

Druhý příklad (se jménem „BSD“ namísto „Linux“) ukazuje, jak se knihovna gexpect chová ve chvíli, kdy nenalezne očekávaný řetězec:

package main
 
import (
        "log"

        "github.com/ThomasRooney/gexpect"
)
 
func main() {
        child, err := gexpect.Spawn("uname")
        if err != nil {
                log.Fatal(err)
        }
        err = child.Expect("BSD")
        if err != nil {
                log.Fatal(err)
        }
        child.Wait()
}

Výsledek ukazuje (mj.), že se testovaný příkaz ukončil dřív, než byl očekávaný řetězec nalezen:

2019/11/23 19:05:46 read /dev/ptmx: input/output error
exit status 1

13. Přepis příkladu volajícího příkaz curl

Jen pro úplnost si ukažme i přepis demonstračního příkladu ze šesté kapitoly:

package main
 
import (
        "log"

        "github.com/ThomasRooney/gexpect"
)
 
func main() {
        child, err := gexpect.Spawn("curl -X HEAD -v github.com")
        if err != nil {
                log.Fatal(err)
        }
        err = child.Expect("Location: https://github.com/")
        if err != nil {
                log.Fatal(err)
        }
        child.Wait()
}

14. Ovládání interaktivní hry spuštěné přes telnet

Hru Zombie MUD, s jejíž existencí jsme se již seznámili v sedmé kapitole, můžeme spustit (resp. přesněji řečeno připojit se k ní) a ovládat s využitím knihovny goexpect relativně snadno. Prozatím si uvedeme variantu, v níž se neustále opakují testy úspěšnosti jednotlivých příkazů:

package main
 
import (
        "log"

        "github.com/ThomasRooney/gexpect"
)
 
func main() {
        child, err := gexpect.Spawn("telnet zombiemud.org")
        if err != nil {
                log.Fatal(err)
        }
        err = child.Expect("... online since 1994")
        if err != nil {
                log.Fatal(err)
        }
        err = child.Expect("Your choice or name:")
        if err != nil {
                log.Fatal(err)
        }
        child.Send("d\n")
        err = child.Expect("Ok, see you later!")
        if err != nil {
                log.Fatal(err)
        }
 
        child.Wait()
}

Namísto příkazu:

child.Send("d\n")

je výhodnější použít příkaz:

child.SendLine("d")

Takže výše uvedený příklad můžeme nepatrně upravit a vylepšit:

package main
 
import (
        "log"

        "github.com/ThomasRooney/gexpect"
)
 
func main() {
        child, err := gexpect.Spawn("telnet zombiemud.org")
        if err != nil {
                log.Fatal(err)
        }
        err = child.Expect("... online since 1994")
        if err != nil {
                log.Fatal(err)
        }
        err = child.Expect("Your choice or name:")
        if err != nil {
                log.Fatal(err)
        }
        child.SendLine("d")
        err = child.Expect("Ok, see you later!")
        if err != nil {
                log.Fatal(err)
        }

        child.Wait()
}

15. Ovládání interpretru Pythonu

Pro ovládání interpretru Pythonu si připravíme několik pomocných funkcí, které nám zjednoduší vlastní zápis „skriptu“. Bude se jednat o funkci pro poslání příkazu (ukončeného Enterem) s kontrolou, zda se poslání podařilo:

func sendCommand(child *gexpect.ExpectSubprocess, command string) {
        err := child.SendLine(command)
        if err != nil {
                log.Fatal(err)
        }
}

A dále o dvojici funkcí, které očekávají obecný výstup produkovaný interpretrem, popř. konkrétně výzvu (prompt):

func expectOutput(child *gexpect.ExpectSubprocess, output string) {
        err := child.Expect(output)
        if err != nil {
                log.Fatal(err)
        }
}
 
func expectPrompt(child *gexpect.ExpectSubprocess) {
        expectOutput(child, ">>> ")
}

Samotný „skript“, který do interpretru vloží dvojici aritmetických výrazů a bude očekávat a testovat jejich výsledek, se tak zkrátí (včetně příkazu pro ukončení interpretru):

expectPrompt(child)
sendCommand(child, "1+2")
expectOutput(child, "3")
 
expectPrompt(child)
sendCommand(child, "6*7")
expectOutput(child, "42")
 
expectPrompt(child)
sendCommand(child, "quit()")

Podívejme se nyní na úplný zdrojový kód takto upraveného příkladu. Je přehlednější, než tomu bylo v případě použití knihovny go-expect:

package main
 
import (
        "log"

        "github.com/ThomasRooney/gexpect"
)
 
func expectOutput(child *gexpect.ExpectSubprocess, output string) {
        err := child.Expect(output)
        if err != nil {
                log.Fatal(err)
        }
}
 
func expectPrompt(child *gexpect.ExpectSubprocess) {
        expectOutput(child, ">>> ")
}
 
func sendCommand(child *gexpect.ExpectSubprocess, command string) {
        err := child.SendLine(command)
        if err != nil {
                log.Fatal(err)
        }
}
 
func main() {
        child, err := gexpect.Spawn("python")
        if err != nil {
                log.Fatal(err)
        }
 
        expectPrompt(child)
        sendCommand(child, "1+2")
        expectOutput(child, "3")
 
        expectPrompt(child)
        sendCommand(child, "6*7")
        expectOutput(child, "42")
 
        expectPrompt(child)
        sendCommand(child, "quit()")
 
        child.Wait()
}

16. Detekce, který interpret Pythonu byl spuštěn

V předchozím textu jsme se seznámili s tím, jakým způsobem je možné s využitím knihovny go-expect zjistit, který interpret Pythonu je spuštěn. V knihovně gexpect k tomuto účelu použijeme regulární výraz:

strs, err := child.ExpectRegexFind("Python [23]")
if err != nil {
        log.Fatal(err)
}

Pokud se nevrátí chyba, bude proměnná strs obsahovat všechny řetězce odpovídající uvedenému regulárnímu výrazu, které byly na standardním výstupu aplikace detekovány. Nás bude zajímat první (a jediný!) výskyt, tedy například:

if strs[0] == "Python 2" {
        log.Println("Python 2")
        ...
        ...
        ...
} else {
        log.Println("Python 3")
        ...
        ...
        ...
}

Příklad použití této konstrukce při testování příkazu print (Python 2) nebo funkce print() (Python 3):

package main
 
import (
        "log"
        "time"

        "github.com/ThomasRooney/gexpect"
)
 
func expectOutput(child *gexpect.ExpectSubprocess, output string) {
        err := child.ExpectTimeout(output, time.Second)
        if err != nil {
                log.Fatal(err)
        }
}
 
func expectPrompt(child *gexpect.ExpectSubprocess) {
        expectOutput(child, ">>> ")
}
 
func sendCommand(child *gexpect.ExpectSubprocess, command string) {
        err := child.SendLine(command)
        if err != nil {
                log.Fatal(err)
        }
}
 
func main() {
        child, err := gexpect.Spawn("python")
        if err != nil {
                log.Fatal(err)
        }
 
        strs, err := child.ExpectRegexFind("Python [23]")
        if err != nil {
                log.Fatal(err)
        }
 
        if strs[0] == "Python 2" {
                log.Println("Python 2")
                expectPrompt(child)
                sendCommand(child, "print 1,2,3")
                expectOutput(child, "1 2 3")
        } else {
                log.Println("Python 3")
                expectPrompt(child)
                sendCommand(child, "print(1,2,3)")
                expectOutput(child, "1 2 3")
        }
 
        expectPrompt(child)
        sendCommand(child, "quit()")
 
        child.Wait()
}

17. Kombinace knihoven testing a gexpect

Pochopitelně nám nic nebrání využít knihovnu testing společně s knihovnou gexpect. Předchozí příklad lze přepsat do formy jednotkových testů následovně:

package main
 
import (
        "testing"
        "time"

        "github.com/ThomasRooney/gexpect"
)
 
func expectOutput(t *testing.T, child *gexpect.ExpectSubprocess, output string) {
        err := child.ExpectTimeout(output, time.Second)
        if err != nil {
                t.Fatal(err)
        }
}
 
func expectPrompt(t *testing.T, child *gexpect.ExpectSubprocess) {
        expectOutput(t, child, ">>> ")
}
 
func sendCommand(t *testing.T, child *gexpect.ExpectSubprocess, command string) {
        err := child.SendLine(command)
        if err != nil {
                t.Fatal(err)
        }
}
 
func TestPythonInterpreter(t *testing.T) {
        child, err := gexpect.Spawn("python")
        if err != nil {
                t.Fatal(err)
        }
 
        strs, err := child.ExpectRegexFind("Python [23]")
        if err != nil {
                t.Fatal(err)
        }
 
        if strs[0] == "Python 2" {
                t.Log("Python 2")
                expectPrompt(t, child)
                sendCommand(t, child, "print 1,2,3")
                expectOutput(t, child, "1 2 3")
        } else {
                t.Log("Python 3")
                expectPrompt(t, child)
                sendCommand(t, child, "print(1,2,3)")
                expectOutput(t, child, "1 2 3")
        }
 
        expectPrompt(t, child)
        sendCommand(t, child, "quit()")
 
        child.Wait()
}

Pro spuštění takto naprogramovaných testů je nutné použít příkaz go test -v a nikoli go run:

=== RUN   TestPythonInterpreter
--- PASS: TestPythonInterpreter (0.03s)
    08_python_test.go:40: Python 2
PASS
ok      command-line-arguments  0.032s

18. Test korektnosti výpočtů prováděných intepretrem Pythonu podruhé

V jedenácté kapitole jsme se seznámili s utilitou (jednalo se o test) sloužící pro zjištění, zda v interpretru Pythonu pracuje správně aritmetický operátor **. Tento test můžeme přepsat takovým způsobem, aby se v něm použila jak standardní knihovna testing, tak i knihovna gexpect:

ict ve školství 24

package main
 
import (
        "fmt"
        "testing"
        "time"

        "github.com/ThomasRooney/gexpect"
)
 
func expectOutput(t *testing.T, child *gexpect.ExpectSubprocess, output string) {
        err := child.ExpectTimeout(output, time.Second)
        if err != nil {
                t.Fatal(err)
        }
}
 
func expectPrompt(t *testing.T, child *gexpect.ExpectSubprocess) {
        expectOutput(t, child, ">>> ")
}
 
func sendCommand(t *testing.T, child *gexpect.ExpectSubprocess, command string) {
        err := child.SendLine(command)
        if err != nil {
                t.Fatal(err)
        }
}
 
func TestPythonInterpreter(t *testing.T) {
        child, err := gexpect.Spawn("python")
        if err != nil {
                t.Fatal(err)
        }
        t.Log("Python interpreter started")
 
        strs, err := child.ExpectRegexFind("Python [23]")
        if err != nil {
                t.Fatal("Python not detected")
        }
        t.Log("Python interpreter detected: " + strs[0])
 
        for i := uint(1); i < 10; i++ {
                sendCommand(t, child, fmt.Sprintf("2**%d", i))
                expectOutput(t, child, fmt.Sprintf("%d", 1<<i))
                t.Logf("Math is ok for input %d", i)
        }
 
        expectPrompt(t, child)
        sendCommand(t, child, "quit()")
 
        child.Wait()
}

Po spuštění výše uvedeného demonstračního příkladu příkazem go test -v by se měly objevit zprávy oznamující, že test funkcionality operátoru ** proběhl v pořádku:

=== RUN   TestPythonInterpreter
--- PASS: TestPythonInterpreter (0.03s)
    09_python_math_test.go:34: Python interpreter started
    09_python_math_test.go:40: Python interpreter detected: Python 2
    09_python_math_test.go:45: Math is ok for input 1
    09_python_math_test.go:45: Math is ok for input 2
    09_python_math_test.go:45: Math is ok for input 3
    09_python_math_test.go:45: Math is ok for input 4
    09_python_math_test.go:45: Math is ok for input 5
    09_python_math_test.go:45: Math is ok for input 6
    09_python_math_test.go:45: Math is ok for input 7
    09_python_math_test.go:45: Math is ok for input 8
    09_python_math_test.go:45: Math is ok for input 9
PASS
ok      command-line-arguments  0.035s

19. Repositář s demonstračními příklady

Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně pět megabajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Příklad Stručný popis Cesta
1 01_check_uname.go základní použití knihovny go-expect https://github.com/tisnik/go-root/blob/master/article43/go-expect/01_check_uname.go
2 02_check_uname_timeout.go nastavení timeoutu při čekání na řetězec, který se má na konzoli objevit https://github.com/tisnik/go-root/blob/master/article43/go-expect/02_check_uname_timeout.go
3 03_check_curl_output.go test výstupu příkazu curl https://github.com/tisnik/go-root/blob/master/article43/go-expect/03_check_curl_output.go
4 04_telnet_game.go ovládání hry dostupné přes telnet https://github.com/tisnik/go-root/blob/master/article43/go-expect/04_telnet_game.go
5 05_python.go ovládání interpretu programovacího jazyka Python https://github.com/tisnik/go-root/blob/master/article43/go-expect/05_python.go
6 06_python_timeout.go ovládání interpretu programovacího jazyka Python, rozhodnutí o verzi Pythonu https://github.com/tisnik/go-root/blob/master/article43/go-expect/06_python_timeout.go
7 07_python_test.go ovládání Pythonu ve formě unit testu https://github.com/tisnik/go-root/blob/master/article43/go-expect/07_python_test.go
8 08_python_math_test.go test aritmetických schopností interpretru Pythonu https://github.com/tisnik/go-root/blob/master/article43/go-expect/08_python_math_test.go
       
9 01_check_uname_linux.go základní použití knihovny go-expect https://github.com/tisnik/go-root/blob/master/article43/gex­pect/01_check_uname_linux­.go
10 02_check_uname_bsd.go základní použití knihovny go-expect https://github.com/tisnik/go-root/blob/master/article43/gex­pect/02_check_uname_bsd.go
11 03_curl_output.go test výstupu příkazu curl https://github.com/tisnik/go-root/blob/master/article43/gex­pect/03_curl_output.go
12 04_telnet_game.go ovládání hry dostupné přes telnet https://github.com/tisnik/go-root/blob/master/article43/gex­pect/04_telnet_game.go
13 05_telnet_game.go vylepšené ovládání hry dostupné přes telnet https://github.com/tisnik/go-root/blob/master/article43/gex­pect/05_telnet_game.go
14 06_python.go ovládání interpretru Pythonu https://github.com/tisnik/go-root/blob/master/article43/gex­pect/06_python.go
15 07_python.go ovládání Pythonu ve formě unit testu https://github.com/tisnik/go-root/blob/master/article43/gex­pect/07_python.go
16 08_python_test.go test aritmetických schopností interpretru Pythonu https://github.com/tisnik/go-root/blob/master/article43/gex­pect/08_python_test.go
17 09_python_math_test.go https://github.com/tisnik/go-root/blob/master/article43/gex­pect/09_python_math_test.go

20. Odkazy na Internetu

  1. Balíček goexpect
    https://github.com/google/goexpect
  2. Balíček go-expect
    https://github.com/Netflix/go-expect
  3. Balíček gexpect
    https://github.com/Thomas­Rooney/gexpect
  4. Expect (originál naprogramovaný v TCL)
    https://core.tcl-lang.org/expect/index
  5. Expect (Wikipedia)
    https://en.wikipedia.org/wiki/Expect
  6. Pexpect
    https://pexpect.readthedoc­s.io/en/stable/
  7. Golang SSH Client: Multiple Commands, Crypto & Goexpect Examples
    http://networkbit.ch/golang-ssh-client/
  8. goblin na GitHubu
    https://github.com/franela/goblin
  9. Mocha framework
    https://mochajs.org/
  10. frisby na GitHubu
    https://github.com/verdverm/frisby
  11. package frisby
    https://godoc.org/github.com/ver­dverm/frisby
  12. Frisby alternatives and similar packages (generováno)
    https://go.libhunt.com/frisby-alternatives
  13. Cucumber for golang
    https://github.com/DATA-DOG/godog
  14. How to Use Godog for Behavior-driven Development in Go
    https://semaphoreci.com/com­munity/tutorials/how-to-use-godog-for-behavior-driven-development-in-go
  15. Comparative Analysis Of GoLang Testing Frameworks
    https://www.slideshare.net/Dushy­antBhalgami/comparative-analysis-of-golang-testing-frameworks
  16. A Quick Guide to Testing in Golang
    https://caitiem.com/2016/08/18/a-quick-guide-to-testing-in-golang/
  17. Tom's Obvious, Minimal Language.
    https://github.com/toml-lang/toml
  18. xml.org
    http://www.xml.org/
  19. Soubory .properties
    https://en.wikipedia.org/wi­ki/.properties
  20. Soubory INI
    https://en.wikipedia.org/wi­ki/INI_file
  21. JSON to YAML
    https://www.json2yaml.com/
  22. Data Format Converter
    https://toolkit.site/format.html
  23. Viper na GitHubu
    https://github.com/spf13/viper
  24. GoDotEnv na GitHubu
    https://github.com/joho/godotenv
  25. The fantastic ORM library for Golang
    http://gorm.io/
  26. Dokumentace k balíčku gorilla/mux
    https://godoc.org/github.com/go­rilla/mux
  27. Gorilla web toolkitk
    http://www.gorillatoolkit.org/
  28. Metric types
    https://prometheus.io/doc­s/concepts/metric_types/
  29. Histograms with Prometheus: A Tale of Woe
    http://linuxczar.net/blog/2017/06/15/pro­metheus-histogram-2/
  30. Why are Prometheus histograms cumulative?
    https://www.robustperception.io/why-are-prometheus-histograms-cumulative
  31. Histograms and summaries
    https://prometheus.io/doc­s/practices/histograms/
  32. Instrumenting Golang server in 5 min
    https://medium.com/@gsisi­mogang/instrumenting-golang-server-in-5-min-c1c32489add3
  33. Semantic Import Versioning in Go
    https://www.aaronzhuo.com/semantic-import-versioning-in-go/
  34. Sémantické verzování
    https://semver.org/
  35. Getting started with Go modules
    https://medium.com/@fonse­ka.live/getting-started-with-go-modules-b3dac652066d
  36. Create projects independent of $GOPATH using Go Modules
    https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o
  37. Anatomy of Modules in Go
    https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16
  38. Modules
    https://github.com/golang/go/wi­ki/Modules
  39. Go Modules Tutorial
    https://tutorialedge.net/golang/go-modules-tutorial/
  40. Module support
    https://golang.org/cmd/go/#hdr-Module_support
  41. Go Lang: Memory Management and Garbage Collection
    https://vikash1976.wordpres­s.com/2017/03/26/go-lang-memory-management-and-garbage-collection/
  42. Golang Internals, Part 4: Object Files and Function Metadata
    https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html
  43. What is REPL?
    https://pythonprogramminglan­guage.com/repl/
  44. What is a REPL?
    https://codewith.mu/en/tu­torials/1.0/repl
  45. Programming at the REPL: Introduction
    https://clojure.org/guides/re­pl/introduction
  46. What is REPL? (Quora)
    https://www.quora.com/What-is-REPL
  47. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  48. Read-eval-print loop (Wikipedia)
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  49. Vim as a Go (Golang) IDE using LSP and vim-go
    https://octetz.com/posts/vim-as-go-ide
  50. gopls
    https://github.com/golang/go/wi­ki/gopls
  51. IDE Integration Guide
    https://github.com/stamble­rre/gocode/blob/master/doc­s/IDE_integration.md
  52. How to instrument Go code with custom expvar metrics
    https://sysdig.com/blog/golang-expvar-custom-metrics/
  53. Golang expvar metricset (Metricbeat Reference)
    https://www.elastic.co/gu­ide/en/beats/metricbeat/7­.x/metricbeat-metricset-golang-expvar.html
  54. Package expvar
    https://golang.org/pkg/expvar/#NewInt
  55. Java Platform Debugger Architecture: Overview
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jpda/jpda­.html
  56. The JVM Tool Interface (JVM TI): How VM Agents Work
    https://www.oracle.com/technet­work/articles/javase/index-140680.html
  57. JVM Tool Interface Version 11.0
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jvmti­.html
  58. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  59. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  60. IBM JVMTI extensions
    http://publib.boulder.ibm­.com/infocenter/realtime/v2r0/in­dex.jsp?topic=%2Fcom.ibm.sof­trt.doc%2Fdiag%2Ftools%2Fjvmti_ex­tensions.html
  61. Go & cgo: integrating existing C code with Go
    http://akrennmair.github.io/golang-cgo-slides/#1
  62. Using cgo to call C code from within Go code
    https://wenzr.wordpress.com/2018/06/07/u­sing-cgo-to-call-c-code-from-within-go-code/
  63. Package trace
    https://golang.org/pkg/runtime/trace/
  64. Introducing HTTP Tracing
    https://blog.golang.org/http-tracing
  65. Command trace
    https://golang.org/cmd/trace/
  66. A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
    https://github.com/wesovilabs/koazee
  67. Funkce vyššího řádu v knihovně Underscore
    https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/
  68. Delve: a debugger for the Go programming language.
    https://github.com/go-delve/delve
  69. Příkazy debuggeru Delve
    https://github.com/go-delve/delve/tree/master/Do­cumentation/cli
  70. Debuggery a jejich nadstavby v Linuxu
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  71. Debuggery a jejich nadstavby v Linuxu (2. část)
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  72. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  73. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  74. Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/
  75. Debugging Go Code with GDB
    https://golang.org/doc/gdb
  76. Debugging Go (golang) programs with gdb
    https://thornydev.blogspot­.com/2014/01/debugging-go-golang-programs-with-gdb.html
  77. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  78. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  79. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  80. The LLDB Debugger
    http://lldb.llvm.org/
  81. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  82. 13 Linux Debuggers for C++ Reviewed
    http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817
  83. Go is on a Trajectory to Become the Next Enterprise Programming Language
    https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e
  84. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  85. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  86. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  87. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  88. 10 tools written in Go that every developer needs to know
    https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/
  89. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  90. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  91. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  92. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  93. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  94. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  95. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  96. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  97. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  98. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  99. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  100. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  101. go-cron
    https://github.com/rk/go-cron
  102. gocron
    https://github.com/jasonlvhit/gocron
  103. clockwork
    https://github.com/whiteShtef/cloc­kwork
  104. clockwerk
    https://github.com/onatm/clockwerk
  105. JobRunner
    https://github.com/bamzi/jobrunner
  106. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  107. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  108. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  109. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  110. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  111. go-prompt
    https://github.com/c-bata/go-prompt
  112. readline
    https://github.com/chzyer/readline
  113. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  114. go-readline
    https://github.com/fiorix/go-readline
  115. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  116. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  117. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  118. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  119. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  120. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  121. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  122. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  123. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  124. Editline Library (libedit)
    http://thrysoee.dk/editline/
  125. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  126. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  127. WinEditLine
    http://mingweditline.sourceforge.net/
  128. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  129. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  130. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  131. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  132. history(3) – Linux man page
    https://linux.die.net/man/3/history
  133. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  134. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  135. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  136. Balíček ogletest
    https://github.com/jacobsa/ogletest
  137. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  138. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  139. package testing
    https://golang.org/pkg/testing/
  140. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  141. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  142. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  143. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  144. GoConvey
    http://goconvey.co/
  145. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  146. 5 simple tips and tricks for writing unit tests in #golang
    https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742
  147. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  148. package gg
    https://godoc.org/github.com/fo­gleman/gg
  149. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  150. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  151. The Go image package
    https://blog.golang.org/go-image-package
  152. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  153. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  154. YAML
    https://yaml.org/
  155. edn
    https://github.com/edn-format/edn
  156. Smile
    https://github.com/FasterXML/smile-format-specification
  157. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  158. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  159. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  160. Introducing JSON
    http://json.org/
  161. Package json
    https://golang.org/pkg/encoding/json/
  162. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  163. Go by Example: JSON
    https://gobyexample.com/json
  164. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  165. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  166. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  167. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  168. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  169. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  170. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  171. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  172. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  173. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  174. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  175. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  176. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  177. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  178. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  179. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  180. Algorithms to Go
    https://yourbasic.org/
  181. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  182. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/
  183. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  184. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  185. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  186. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  187. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  188. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  189. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  190. The Go Programming Language (home page)
    https://golang.org/
  191. GoDoc
    https://godoc.org/
  192. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  193. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  194. The Go Programming Language Specification
    https://golang.org/ref/spec
  195. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  196. Package builtin
    https://golang.org/pkg/builtin/
  197. Package fmt
    https://golang.org/pkg/fmt/
  198. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  199. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  200. Learning Go
    https://www.miek.nl/go/
  201. Go Bootcamp
    http://www.golangbootcamp.com/
  202. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  203. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  204. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  205. The Go Blog
    https://blog.golang.org/
  206. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  207. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  208. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  209. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  210. How the Go runtime implements maps efficiently (without generics)
    https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics
  211. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  212. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  213. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  214. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  215. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  216. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  217. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  218. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  219. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  220. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  221. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  222. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  223. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  224. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  225. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  226. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  227. Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
    https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/
  228. 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
    https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd
  229. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  230. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  231. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  232. Go vs. Python
    https://www.peterbe.com/plog/govspy
  233. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  234. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  235. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  236. Go by Example: Slices
    https://gobyexample.com/slices
  237. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  238. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  239. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  240. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  241. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  242. nils In Go
    https://go101.org/article/nil.html
  243. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  244. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  245. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  246. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  247. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  248. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  249. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  250. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  251. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  252. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  253. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  254. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  255. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  256. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  257. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  258. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  259. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  260. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  261. Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
    https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06
  262. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  263. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  264. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  265. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  266. Selectors
    https://golang.org/ref/spec#Selectors
  267. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  268. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  269. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  270. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  271. Part 21: Goroutines
    https://golangbot.com/goroutines/
  272. Part 22: Channels
    https://golangbot.com/channels/
  273. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  274. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  275. Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
    https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/
  276. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  277. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  278. Control Structures
    https://www.golang-book.com/books/intro/5
  279. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  280. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  281. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  282. Goroutine IDs
    https://blog.sgmansfield.com/2015/12/go­routine-ids/
  283. Different ways to pass channels as arguments in function in go (golang)
    https://stackoverflow.com/qu­estions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang
  284. justforfunc #22: using the Go execution tracer
    https://www.youtube.com/wat­ch?v=ySy3sR1LFCQ
  285. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  286. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  287. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  288. Effective Go
    https://golang.org/doc/ef­fective_go.html
  289. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  290. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation
  291. The zero value of a slice is not nil
    https://stackoverflow.com/qu­estions/30806931/the-zero-value-of-a-slice-is-not-nil
  292. Go-tcha: When nil != nil
    https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic
  293. Nils in Go
    https://www.doxsey.net/blog/nils-in-go

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.