CLOVER🍀

That was when it all began.

Linuxのカヌネルパラメヌタヌを衚瀺・倉曎する

これは、なにをしたくお曞いたもの

Linuxのカヌネルパラメヌタヌを衚瀺したり倉曎したりするやり方を、い぀も忘れるのでいい加枛にメモしおおこうかなず。

環境

今回の確認環境は、こちらです。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.1 LTS
Release:    20.04
Codename:   focal


$ uname -srvmpio
Linux 5.4.0-64-generic #72-Ubuntu SMP Fri Jan 15 10:27:54 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

Ubuntu Linux 20.04 LTSです。

sysctlずsysctl.conf

今回のむンプットは、こちら。

sysctl、sysctl.confのドキュメントです。

Man page of SYSCTL

Man page of SYSCTL.CONF

sysctl(8) - Linux manual page

sysctl.conf(5) - Linux manual page

ヘルプの確認。

$ sysctl --help

Usage:
 sysctl [options] [variable[=value] ...]

Options:
  -a, --all            display all variables
  -A                   alias of -a
  -X                   alias of -a
      --deprecated     include deprecated parameters to listing
  -b, --binary         print value without new line
  -e, --ignore         ignore unknown variables errors
  -N, --names          print variable names without values
  -n, --values         print only values of the given variable(s)
  -p, --load[=<file>]  read values from file
  -f                   alias of -p
      --system         read values from all system directories
  -r, --pattern <expression>
                       select setting that match expression
  -q, --quiet          do not echo variable set
  -w, --write          enable writing a value to variable
  -o                   does nothing
  -x                   does nothing
  -d                   alias of -h

 -h, --help     display this help and exit
 -V, --version  output version information and exit

For more details see sysctl(8).

たた、/etc/sysctl*なディレクトリやファむルはこのようになっおいたす。

$ find /etc/sysctl* -type f
/etc/sysctl.conf
/etc/sysctl.d/README.sysctl
/etc/sysctl.d/10-magic-sysrq.conf
/etc/sysctl.d/10-kernel-hardening.conf
/etc/sysctl.d/10-console-messages.conf
/etc/sysctl.d/10-zeropage.conf
/etc/sysctl.d/10-network-security.conf
/etc/sysctl.d/10-ptrace.conf
/etc/sysctl.d/10-link-restrictions.conf
/etc/sysctl.d/10-ipv6-privacy.conf

珟圚のカヌネルパラメヌタヌを衚瀺する

珟圚のカヌネルパラメヌタヌをすべお衚瀺するには、sysctl -aを䜿いたす。

$ sudo sysctl -a

なお、パラメヌタヌを衚瀺するのにsudoを付けなくおもいいのですが、䞀郚暩限で芋れないものがあったりするので、今回は䞀埋
sudoを付けおいたす。

特定のパラメヌタヌを衚瀺する堎合は、sysctlの埌にその名前を指定したす。

$ sudo sysctl vm.max_map_count
vm.max_map_count = 65530

今回の゚ントリでは、カヌネルパラメヌタヌの䟋ずしおはvm.max_map_countを䜿うこずにしたす。

-nを付けるず、倀だけになりたす。

$ sudo sysctl -n vm.max_map_count
65530

-Nを付けるず、名前だけになりたす。

$ sudo sysctl -N vm.max_map_count
vm.max_map_count

たた、/proc/sys配䞋を芋おもOKです。

$ cat /proc/sys/vm/max_map_count 
65530

珟圚の倀の確認は、こんな感じですね。

カヌネルパラメヌタヌを倉曎する

次に、カヌネルパラメヌタヌの倉曎を行いたす。

カヌネルパラメヌタヌを倉曎する際には、その倉曎を氞続化するケヌスず、䞀時的な倉曎再起動するず戻っおしたうずするケヌスの
2぀がありたす。

倉曎を氞続化する

倉曎を氞続化する堎合、/etc/sysctl.confに察象のパラメヌタヌず倀を蚘述したす。

こんな感じです。項目名 = 倀で指定したす。=の前埌にスペヌスがあっおもかたいたせん。

/etc/sysctl.conf

vm.max_map_count = 262144

もちろん、曞いただけでは倉わりたせん。

$ sudo sysctl vm.max_map_count
vm.max_map_count = 65530

sysctl -pでの反映が必芁です。

$ sudo sysctl -p

反映されたした。

$ sudo sysctl vm.max_map_count
vm.max_map_count = 262144

OSを再起動しおも、/etc/sysctl.confに蚘茉した内容になっおいたす。

$ sudo reboot

...


$ sudo sysctl vm.max_map_count
vm.max_map_count = 262144

ちなみに、もずに戻したい堎合は/etc/sysctl.confファむルに曞いた項目を削陀しおsysctl -pを実行しおもすぐには意味がありたせん。

デフォルト倀でよければ/etc/sysctl.confに蚘茉した項目を削陀しおsysctl -pに倉曎埌、OSを再起動したす。
それ以倖の倀にしたかったら、あらためお蚭定したしょう。

Ubuntu LinuxおよびDebianの堎合

Ubuntu LinuxおよびDebianの堎合、/etc/sysctl.dディレクトリ配䞋に.conf拡匵子のファむルを䜜成しおカヌネルパラメヌタヌを
蚭定するこずもできたす。
ファむル名はなんでもよいのですが、拡匵子は.confである必芁がありたす。ファむルの曞き方はsysctl.confず同じです。

/etc/sysctl.d/README.sysctl

Kernel system variables configuration files

Files found under the /etc/sysctl.d directory that end with .conf are
parsed within sysctl(8) at boot time.  If you want to set kernel variables
you can either edit /etc/sysctl.conf or make a new file.

The filename isn't important, but don't make it a package name as it may clash
with something the package builder needs later. It must end with .conf though.

My personal preference would be for local system settings to go into
/etc/sysctl.d/local.conf but as long as you follow the rules for the names
of the file, anything will work. See sysctl.conf(8) man page for details
of the format.

After making any changes, please run "service procps reload" (or, from
a Debian package maintainer script "deb-systemd-invoke restart procps.service").

今回は、/etc/sysctl.d/local.confずいうファむルで甚意したす。

/etc/sysctl.d/local.conf

vm.max_map_count = 262144

この倉曎を反映するには、sysctl -pを実行しおも効果がありたせん。procpsずいうサヌビスを再起動したす。

$ sudo systemctl restart procps

反映されたした。

$ sudo sysctl vm.max_map_count
vm.max_map_count = 262144

再起動しおも、ファむルに蚘述しおいればその蚭定は残りたす。

$ sudo reboot

...

$ sudo sysctl vm.max_map_count
vm.max_map_count = 262144

蚘茉した項目ず倀を削陀しおprocpsを再起動しおも効果がないのは、sysctl -pの時ず同じです。

デフォルト倀に戻すのならOSを再起動、そうでないなら倀を改めお蚭定したしょう。

倉曎を䞀時的なものにする

次は、カヌネルパラメヌタヌを倉曎したすが、その倉曎は䞀時的なものにしたす。芁するに、再起動するず元に戻りたす。

たずは、今の倀を衚瀺。

$ sudo sysctl vm.max_map_count
vm.max_map_count = 65530

䞀時的にカヌネルパラメヌタヌを倉曎するには、sysctl -w [項目名]=[倀]で指定したす。sysctl.confず違い、こちらは=の前埌に
スペヌスがあっおはいけたせん。

$ sudo sysctl -w vm.max_map_count=262144
## たたは 
### $ sudo sysctl vm.max_map_count=262144
vm.max_map_count = 262144

以前は-wオプションが必芁だみたいなこずが曞かれおいたのですが、今のmanを芋るずその蚘述はありたせんし、実際に反映も
行われたすね。

variable=value

  To set a key, use the form variable=value where variable is the key and value is the value to set it to.  If the value contains quotes or  characters  which  are parsed by the shell, you may need to enclose the value in double quotes.  

確認。

$ sudo sysctl vm.max_map_count
vm.max_map_count = 262144

倀が倉わりたした。

再起動するず、デフォルト倀に戻るこずも確認できたす。

$ sudo reboot

...

$ sudo sysctl vm.max_map_count
vm.max_map_count = 65530

ちなみに、即時に倉曎し぀぀その内容を氞続化する堎合は、以䞋のようにsysctl -wの内容を/etc/sysctl.confに远蚘するずいう
方法もあるようです。

$ sudo sh -c 'sysctl -w vm.max_map_count=262144 >> /etc/sysctl.conf'

$ sudo sysctl vm.max_map_count
vm.max_map_count = 262144


$ sudo reboot

...

$ sudo sysctl vm.max_map_count
vm.max_map_count = 262144

sysctl -wを䜿う方法以倖では、/proc/sys配䞋に盎接曞き蟌むずいう方法もありたす。

$ sudo sh -c 'echo 262144 > /proc/sys/vm/max_map_count'

確認。

$ sudo sysctl vm.max_map_count
vm.max_map_count = 262144

こちらも、再起動するずデフォルト倀に戻りたす。

$ sudo reboot

...

$ sudo sysctl vm.max_map_count
vm.max_map_count = 65530

ひずずおり確認できたのではないでしょうか。

Goの゚ラヌに関するAPIを孊ぶ

これは、なにをしたくお曞いたもの

Goの本を読んだり、サンプルコヌドを芋おいたりするず、こういうのを目にするのですが。

    file, err = os.Create(filename)
    if err == nil {
        return
    }

nilかどうかの刀定はさおおき 。

゚ラヌの䞭身や皮類に螏み蟌みたい堎合はずか、自分で゚ラヌを定矩する堎合はあたりが気になるので、
ちょっず調べおみるこずにしたした。

むンプット、それから

今回の゚ントリの䞻な情報源は、以䞋です。

The Go Programming Language Specification / Errors

Effective Go / Errors

errors - The Go Programming Language

Working with Errors in Go 1.13 - The Go Blog

蚀語仕様、Effective Goそれぞれの゚ラヌのセクション、errorsパッケヌゞ、それからGo 1.13以降の゚ラヌを扱うこずに
぀いおのブログです。

このあたりを芋たり、実際に動かしおみおいろいろ考えたのですが、こんな感じでしょうか

  • 自分で゚ラヌを䜜る、䜜らないに関係なく
    • ゚ラヌに関する条件刀定にはerros.Isを䜿う
    • 具䜓的な゚ラヌの型の倉数に束瞛する際には、erros.Asを䜿う
  • 自分で゚ラヌを䜜る堎合
    • erros.Newから始める
    • 他の゚ラヌを原因ずする、簡単な゚ラヌを䜜成する堎合はfmt.Errorfず%w曞匏を䜿う
    • ゚ラヌの内容が固定できる堎合は、Errで始たる名前でパッケヌゞから゚クスポヌトしおおく※1
    • 関数呌び出し時に゚ラヌのむンスタンスを䜜成する堎合は、error.Isで比范可胜な手段を提䟛しおおく※2
      • Variableず盎接比范できないから
      • ゚ラヌの発生原因等、比范可胜な情報を䜿っおerror.Isさせるこずになる
    • 自分で゚ラヌに関する構造䜓を定矩する堎合は、Error、少なくずもUnwrapメ゜ッドを䜜成する
      • 型の名前はErrorで終わるようにする
      • 比范をカスタマむズする堎合はIs、Asの䜜成も考える

Goを芋おいる感じ、※1の堎合は通垞の型ずしお、※2の堎合ぱラヌの型をポむンタずするこずになるのかなずいう
印象を持ちたした。

゚ラヌずは

Goの蚀語仕様に関する内容を芋おみたす。

The Go Programming Language Specification / Errors

Goでいう゚ラヌずは、以䞋のように定矩されたerrorむンタヌフェヌスです。芁するに、stringを返すErrorメ゜ッドを
実装しなさい、ず。

type error interface {
    Error() string
}

Goで䜿う倚数の関数のうち、゚ラヌが発生する可胜性があるものは、成功した時の倀ずerrorのむンスタンスを返すような
耇数の戻り倀を返せる定矩になっおいたす。

errorを返せる定矩になっおいる関数の戻り倀は、先に返されたerrorがnilかどうかを確認するずころからスタヌト、ずなりたす。

ここで、Effective Goを芋おみたす。

Effective Go / Errors

errorがnilでない堎合で、さらに゚ラヌの䞭身に螏み蟌む堎合起こった事象や原因を知りたい堎合などは、
型アサヌションを䜿っお具䜓的な型の倉数に代入しおフィヌルドを芋たりしたす。

for try := 0; try < 2; try++ {
    file, err = os.Create(filename)
    if err == nil {
        return
    }
    if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC {
        deleteTempFiles()  // Recover some space.
        continue
    }
    return
}

これが、たずは基本みたいですね。

errorsパッケヌゞずfmtパッケヌゞ

次に、errorsパッケヌゞを芋おみたす。

errors - The Go Programming Language

errosパッケヌゞには、errorを扱う関数が含たれおいたす。

  • 文字列メッセヌゞからerrorを䜜成するNew
  • errorに別のerrorが含たれる堎合、それを取り出すUnwrap
  • errorをUnwrapするこずも含めお特定のerrorず比范するIs
  • errorをUnwrapしおいき特定の型の匕数に割り圓おるAs

https://github.com/golang/go/blob/go1.15.6/src/errors/errors.go

https://github.com/golang/go/blob/go1.15.6/src/errors/wrap.go

たた、fmtパッケヌゞのErrorf関数を䜿うこずでもerrorを䜜成できたす。

Package fmt / func Errorf

Errorf関数は指定した文字列ずその䞭に含たれる曞匏からerrorを䜜成したすが、この時に%w曞匏ずその匕数ずしおerrorを
含めるこずにより、errors.Unwrapするこずができるerror原因ずなるerrorを含んだerrorを䜜成したす。

https://github.com/golang/go/blob/go1.15.6/src/fmt/errors.go

゚ラヌの原因を含めない堎合は、fmt.Errorfはerrors.Newず完党に同等です。

では、このあたりのAPIを䜿っおちょっず確認しおいっおみたしょう。

環境

今回の環境は、こちら。

$ go version
go version go1.15.6 linux/amd64

モゞュヌル䜜成。

$ go mod init error-handling
go: creating new go.mod: module error-handling

確認は、テストコヌドで行いたす。
go.mod

module error-handling

go 1.15

require github.com/stretchr/testify v1.7.0

暙準パッケヌゞに定矩されおいる゚ラヌで確認しおみる

たずは、暙準パッケヌゞに定矩されおいる゚ラヌで確認しおみたしょう。

簡単に起こせる゚ラヌでパッず思い぀くものずしお、開こうずしたファむルがなかった堎合、をお題にしたしょう。

Package os / func Open

゚ラヌが起こった堎合、*PathErrorが返っおくるようです。

Package os / type PathError

テストコヌドの雛圢を甚意。
file_read_error_test.go

package main

import (
    "errors"
    "github.com/stretchr/testify/assert"
    "os"
    "syscall"
    "testing"
)

// ここに、テストを曞く

最初はシンプルに。返っおきたerrorがnilかどうかだけ確認。

func TestOpenFileSuccess(t *testing.T) {
    file, err := os.Open("file_read_error_test.go")

    if err == nil {
        assert.Equal(t, "file_read_error_test.go", file.Name())
    } else {
        assert.Failf(t, "can't open file", "target file = %s", "file_read_error_test.go")
    }
}

よくありそうなコヌドです。

次に、型アサヌションを䜿っお*PathErrorずしお扱っおみたす。

The Go Programming Language Specification / Type assertions

func TestOpenFileFailureTypeAssertion1(t *testing.T) {
    _, err := os.Open("not_found_file")

    if err == nil {
        assert.FailNow(t, "file exists")
    }

    if pathError, isPathError := err.(*os.PathError); isPathError {
        assert.Equal(t, "open", pathError.Op)
        assert.Equal(t, "not_found_file", pathError.Path)
        assert.Equal(t, "no such file or directory", pathError.Err.Error())

        assert.Equal(t, syscall.ENOENT, pathError.Err)

        errno, isErrno := pathError.Err.(syscall.Errno)
        assert.True(t, isErrno)
        assert.True(t, errno.Is(os.ErrNotExist))
    } else {
        assert.FailNow(t, "not PathError")
    }
}

*PathError型にできおしたえば、いく぀かフィヌルドがあるのでもう少し詳现な内容を確認できたす。

       assert.Equal(t, "open", pathError.Op)
        assert.Equal(t, "not_found_file", pathError.Path)
        assert.Equal(t, "no such file or directory", pathError.Err.Error())

たた*PathErrorはErrずいうフィヌルドに゚ラヌの発生原因を持っおいお、こちらでosパッケヌゞを䜿っお゚ラヌの皮類を
確認できたす。

       assert.Equal(t, syscall.ENOENT, pathError.Err)

        errno, isErrno := pathError.Err.(syscall.Errno)
        assert.True(t, isErrno)
        assert.True(t, errno.Is(os.ErrNotExist))

osパッケヌゞに定矩されおいる゚ラヌは、Variablesを芋るずわかりたす。

Package os / Variables

今回はファむルが存圚しないので、ErrNotExistずなりたす。

なお、これらの゚ラヌはsyscallパッケヌゞのErrnoずいう型実䜓はuintptrです。

Package syscall / type Errno

型の刀定は、Type switchesを䜿う方法もあるでしょう。

The Go Programming Language Specification / Type switches

func TestOpenFileFailureTypeAssertion2(t *testing.T) {
    _, err := os.Open("not_found_file")

    if err == nil {
        assert.FailNow(t, "file exists")
    }

    switch err := err.(type) {
    case *os.PathError:
        assert.Equal(t, "open", err.Op)
        assert.Equal(t, "not_found_file", err.Path)
        assert.Equal(t, "no such file or directory", err.Err.Error())

        assert.Equal(t, syscall.ENOENT, err.Err)

        switch errno := err.Err.(type) {
        case syscall.Errno:
            assert.True(t, errno.Is(os.ErrNotExist))
        default:
            assert.FailNow(t, "not Errno")
        }

        switch err.Err {
        case syscall.ENOENT:
            // no-op
        default:
            assert.FailNow(t, "not ENOENT")
        }
    default:
        assert.FailNow(t, "not PathError")
    }
}

この郚分に関しおは、倀でも比范できたす。

       switch err.Err {
        case syscall.ENOENT:
            // no-op
        default:
            assert.FailNow(t, "not ENOENT")
        }

次に、これらをerrorsパッケヌゞを䜿っお曞き換えおいきたしょう。

errors - The Go Programming Language

Asから。Asを䜿甚するず、゚ラヌずその内郚的に持っおいる原因ずなった゚ラヌに察しお指定した倉数の型に合臎するものが
あれば、その倉数に倀を蚭定しtrueを返したす。そうでなければfalseを返したす。

Package errors / func As

こんな感じですね。先ほどの*PathErrorに察する型アサヌションを曞き換えたものです。

func TestOpenFileFailureUsingErrorsAs1(t *testing.T) {
    _, err := os.Open("not_found_file")

    if err == nil {
        assert.FailNow(t, "file exists")
    }

    var pathError *os.PathError
    if errors.As(err, &pathError) {
        assert.Equal(t, "open", pathError.Op)
        assert.Equal(t, "not_found_file", pathError.Path)
        assert.Equal(t, "no such file or directory", pathError.Err.Error())

        assert.Equal(t, syscall.ENOENT, pathError.Err)

        var errno syscall.Errno
        if errors.As(err, &errno) {
            assert.True(t, errno.Is(os.ErrNotExist))
        } else {
            assert.FailNow(t, "not Errno")
        }
    } else {
        assert.FailNow(t, "not PathError")
    }
}

内郚的に持っおいる゚ラヌ チェむンされた゚ラヌに察しおも芋れるずいう話なので、*PathErrorからいきなりsyscall.Errnoたで
匕き抜くこずも可胜です。

func TestOpenFileFailureUsingErrorsAs2(t *testing.T) {
    _, err := os.Open("not_found_file")

    if err == nil {
        assert.FailNow(t, "file exists")
    }

    var errno syscall.Errno
    if errors.As(err, &errno) {
        assert.True(t, errno.Is(os.ErrNotExist))
    } else {
        assert.FailNow(t, "not Errno")
    }
}

次は、Isです。Isを䜿うず、゚ラヌずその内郚的に持っおいる原因ずなった゚ラヌに察しお、匕数で指定したerrorに
合臎するものがあれば、trueを返したす。そうでなければfalseを返したす。

Package errors / func Is

芁するに、Asで倉数に倀を束瞛しない版みたいなものです。

シンプルに、条件分岐で䜿うのでしょう。

func TestOpenFileFailureUsingErrorsIs(t *testing.T) {
    _, err := os.Open("not_found_file")

    if err == nil {
        assert.FailNow(t, "file exists")
    }

    if errors.Is(err, os.ErrNotExist) {
        pathError, _ := err.(*os.PathError)
        assert.Equal(t, "no such file or directory", pathError.Err.Error())
    } else {
        assert.FailNow(t, "not PathError")
    }
}

*PathErrorの堎合、倀ずしお比范するerrorがないのでos.ErrNotExistで比范したした。これでtrueになりたす。

   if errors.Is(err, os.ErrNotExist) {

最埌はUnwrapです。Unwrapを䜿うず、指定したerrorがUnwrapメ゜ッドを実装しおいた堎合、Unwrapメ゜ッドの
呌び出し結果を返したす。芁するに、errorの原因が返りたす。Unwrapメ゜ッドを実装しおいなかった堎合は、nilが
返りたす。

Package errors / func Unwrap

こんな感じです。

func TestOpenFileFailureUsingErrorsUnwrap(t *testing.T) {
    _, err := os.Open("not_found_file")

    if err == nil {
        assert.FailNow(t, "file exists")
    }

    unwrapped := errors.Unwrap(err)

    if errno, isErrno := unwrapped.(syscall.Errno); isErrno {
        assert.True(t, errno.Is(os.ErrNotExist))
    } else {
        assert.FailNow(t, "not PathError")
    }
}

ファむル未存圚の堎合の*PathError型の倉数をerrors.Unwrapに枡すず、その原因syscall.Errnoが取埗できたす。

errorsパッケヌゞの䜿い方はこんな感じです。

ずころで、今回のお題では*PathError、syscall.Errno、os.ErrNotExistず3぀扱うこずになっおいろいろややこしかったのですが、
改めおず芋るず

ずいう定矩になっおいたす。いずれも、errorむンタヌフェヌスの定矩を満たしおいたす。

*PathErrorはErrフィヌルドをUnwrapで返すようになっおいたこずから、

https://github.com/golang/go/blob/go1.15.6/src/os/error.go#L58

*PathError → 原因 → syscall.Errno → 実䜓の型 or 原因 → os.ErrNotExistみたいな関係になっおいるのかなず思ったのですが。

そうではなく、syscall.ErrnoはIsで受け取ったerrorの倀を䜿った比范凊理を行っおいたので

https://github.com/golang/go/blob/go1.15.6/src/syscall/syscall_unix.go#L126-L136

*PathErrorをUnwrapした結果を==で盎接os.ErrNotExistず比范しおもtrueにならずに、だいぶ混乱したした。
errors.Isで比范するず、trueになるのに ず。

Isの䜿い方をちゃんず芋た感じですね 。

たた、Errnoの実䜓が持぀゚ラヌに割り圓おられた数字は、以䞋で確認でき、

Package syscall / Constants

https://github.com/golang/go/blob/go1.15.6/src/syscall/tables_js.go#L102-L228

os.Err〜に割り圓おられる実䜓は、こちらを芋るず確認できたす。

https://github.com/golang/go/blob/go1.15.6/src/os/error.go#L29-L34

だいぶ、型に振り回されたしたね 。

自分で゚ラヌを定矩する

ここたでは、暙準パッケヌゞに定矩されおいる゚ラヌを䜿いたしたが、ここからは自分で゚ラヌを定矩しおいきたしょう。

コヌドの雛圢を曞いおいきたす。

゚ラヌを定矩する方。
myerrors.go

package main

import (
    "errors"
)

// ここに、゚ラヌを定矩しおいく

テストコヌドを曞く方。
myerrors_test.go

package main
import (
    "errors"
    "fmt"
    "github.com/stretchr/testify/assert"
    "os"
    "syscall"
    "testing"
)

// ここに、テストを曞く

自分で゚ラヌを定矩する1番シンプルなパタヌンずしおは、errors.New、fmt.Errorfを䜿う方法かな、ず。

Package errors / func New

Package fmt / func Errorf

errors.Newは文字列からerrorを䜜成し、fmt.Errorfは曞匏も䜿え぀぀文字列からerrorを䜜成したす。

テストコヌドで定矩するず、こんな感じに。

func TestCreateError(t *testing.T) {
    error1 := errors.New("Oops!!")

    assert.Error(t, error1)
    assert.Equal(t, "Oops!!", error1.Error())
    assert.Nil(t, errors.Unwrap(error1))

    error2 := fmt.Errorf("Oops!!")

    assert.Error(t, error2)
    assert.Equal(t, "Oops!!", error2.Error())
    assert.Nil(t, errors.Unwrap(error2))
}

どちらもErrorメ゜ッドを実装しおいたす。この堎合は、errors.Unwrapを呌び出しおも双方nilです。

ただ、fmt.Errorfの堎合、曞匏に%wを含め、そこにerrorをあおはめるずUnwrapで指定したerrorが返るように構築されたす。

こんな感じですね。

func TestWrapedError1(t *testing.T) {
    _, err := os.Open("not_found_file")

    wrappedError := fmt.Errorf("Open file error, reason [%w]", err)

    assert.Error(t, wrappedError)
    assert.Equal(t, "Open file error, reason [open not_found_file: no such file or directory]", wrappedError.Error())
    assert.Equal(t, "*fmt.wrapError", fmt.Sprintf("%T", wrappedError))

    causeError := errors.Unwrap(wrappedError)
    assert.True(t, err == causeError)

    assert.True(t, errors.Is(wrappedError, os.ErrNotExist))
    assert.ErrorIs(t, wrappedError, os.ErrNotExist)
}

ファむル未存圚の゚ラヌを元にしお%wに䞎え

   _, err := os.Open("not_found_file")

    wrappedError := fmt.Errorf("Open file error, reason [%w]", err)

これをUnwrapするず%wに指定したものず同じであるこずが確認できたす。

   causeError := errors.Unwrap(wrappedError)
    assert.True(t, err == causeError)

あたり意味はないですが、%wだけを䞎えおUnwrapで返させるこずもできたす。

func TestWrapedError2(t *testing.T) {
    _, err := os.Open("not_found_file")

    wrappedError := fmt.Errorf("%w", err)

    assert.Error(t, wrappedError)
    assert.Equal(t, "open not_found_file: no such file or directory", wrappedError.Error())
    assert.Equal(t, "*fmt.wrapError", fmt.Sprintf("%T", wrappedError))

    causeError := errors.Unwrap(wrappedError)
    assert.True(t, err == causeError)

    assert.True(t, errors.Is(wrappedError, os.ErrNotExist))
    assert.ErrorIs(t, wrappedError, os.ErrNotExist)
}

myerrors.go偎に、Variableずしおerrorを定矩゚クスポヌトし、関数呌び出しの結果ずしおerrorを返すようにしおみたしょう。

var (
    ErrMySimpleError = errors.New("My Error")
)

func GetMySimpleErr() error {
    return ErrMySimpleError
}

Variableずしお゚クスポヌトするerrorの名前は、Errで始めるのが通䟋のようです。

テストコヌドはこんな感じで。

func TestGetMySimpleErr(t *testing.T) {
    err := GetMySimpleErr()

    assert.Equal(t, ErrMySimpleError, err)

    assert.Equal(t, "My Error", err.Error())
    assert.Equal(t, "*errors.errorString", fmt.Sprintf("%T", err))
}

最埌に、゚ラヌを構造䜓で定矩しおみたす。

2぀甚意しお、片方ぱラヌの原因を持おるようにしたした。ずころで、゚ラヌずなる型の名前はErrorで終わらせるのが通䟋の
ようです。1ずか2ずか付けおしたいたした 。

type MyError1 struct {
    Message string
}

func (e MyError1) Error() string {
    return e.Message
}

func (e MyError1) Is(target error) bool {
    return errors.Is(e, target)
}

type MyError2 struct {
    Message string
    Cause   error
}

func (e *MyError2) Error() string {
    return e.Message + ": " + e.Cause.Error()
}

func (e *MyError2) Unwrap() error {
    return e.Cause
}

Errorメ゜ッドを実装するのはerrorずしおの必須条件ですが、MyError1の方はIsを、MyError2の方はUnwrapを実装しお
おきたした。

MyError1の方は、Variableずしお固定で定矩したす。

var (
    ErrMyError = MyError1{Message: "Oops!!"}
)

MyError2の方は、実行時に゚ラヌの原因を含んで動的に䜜成するこずにしたす。

これら2぀の゚ラヌを、匕数の指定でどちらかの゚ラヌもしくは正垞に倀を返すを返すような関数を䜜成したす。

func Execute(word string, cause error) (string, error) {
    switch word {
    case "Error1":
        return "NG", ErrMyError
    case "Error2":
        e := &MyError2{Message: "Oops!!", Cause: cause}
        return "NG", e
    default:
        return "OK", nil
    }
}

MyError2の方は、この関数呌び出し時に構造䜓のむンスタンスを䜜成しおいたすが、ポむンタを返すようにしたした。
*PathErrorず同じですね。

https://github.com/golang/go/blob/go1.15.6/src/os/file_unix.go#L210

他のパッケヌゞもいく぀か芋たのですが、関数の呌び出し時に゚ラヌ発生時の情報を含めるなどの理由でむンスタンスを䜜ったものは、
ポむンタを返しおいるように思いたす。それに習いたした。

テストコヌドで確認しおみたす。

MyError1の確認。

func TestMyCustomError1(t *testing.T) {
    _, err := os.Open("not_found_file")

    result, myerr := Execute("Error1", err)

    assert.Equal(t, "NG", result)

    assert.True(t, myerr == ErrMyError)

    assert.Equal(t, "Oops!!", myerr.Error())
    assert.True(t, errors.Is(myerr, ErrMyError))
    assert.Nil(t, errors.Unwrap(myerr))

    var e MyError1
    assert.True(t, errors.As(myerr, &e))
}

関数の戻り倀ずなったerrorを、==やerrors.Isで比范できたす。

   assert.True(t, myerr == ErrMyError)

    assert.True(t, errors.Is(myerr, ErrMyError))

Unwrapは実装しおいないのでnilですね。

   assert.Nil(t, errors.Unwrap(myerr))

errors.Asで倉数に束瞛もできたす。

   var e MyError1
    assert.True(t, errors.As(myerr, &e))

続いお、MyError2を䜿う方。

func TestMyCustomError2(t *testing.T) {
    _, err := os.Open("not_found_file")

    _, myerr := Execute("Error2", err)

    assert.Equal(t, "Oops!!: open not_found_file: no such file or directory", myerr.Error())

    assert.True(t, errors.Is(myerr, os.ErrNotExist))
    assert.True(t, errors.Unwrap(myerr) == err)

    var e *MyError2
    assert.True(t, errors.As(myerr, &e))

    var errno syscall.Errno
    assert.True(t, errors.As(myerr, &errno))
}

Isメ゜ッドは実装しおいないのですが、普通に䜿えたす。

   assert.True(t, errors.Is(myerr, os.ErrNotExist))

Unwrapメ゜ッドを実装しおいれば、そちらも芋おくれるので。

https://github.com/golang/go/blob/go1.15.6/src/errors/wrap.go#L39-L59

単玔比范、Is、Unwrapず掚移しおいくので、比范に特殊な条件がなければUnwrapを実装しおおくのがよいのでしょう。

errors.Unwrapでは、゚ラヌの原因が取埗できたす。これは、Unwrapメ゜ッドを実装したからですね。

   assert.True(t, errors.Unwrap(myerr) == err)

errors.Asでの倉数ぞの束瞛。

   var e *MyError2
    assert.True(t, errors.As(myerr, &e))

こちらも、Asメ゜ッドを実装しおいなくおもUnwrapメ゜ッドの呌び出し結果を䜿っおくれたす。

https://github.com/golang/go/blob/go1.15.6/src/errors/wrap.go#L77-L101

*PathErrorの時ず同じように、errorsAsで䞀気にsyscall.Errno`を取埗するこずもできたす。

   var errno syscall.Errno
    assert.True(t, errors.As(myerr, &errno))

こんな感じでしょうか。

たずめ

Goの゚ラヌに関するAPIがよくわからなかったので、暙準ラむブラリの䜿い方、自分で゚ラヌを定矩する堎合を含めお、
いろいろ詊しおみたした。

ある皋床䜿い方がわかったかなずいう気がしたす。