github.com/ericwq/aprilsh@v0.0.0-20240517091432-958bc568daa0/frontend/server/server_linux_test.go (about) 1 // Copyright 2022~2023 wangqi. All rights reserved. 2 // Use of this source code is governed by a MIT-style 3 // license that can be found in the LICENSE file. 4 5 //go:build linux 6 7 package main 8 9 import ( 10 "os" 11 "reflect" 12 "strings" 13 "testing" 14 15 "github.com/creack/pty" 16 "github.com/ericwq/aprilsh/util" 17 utmps "github.com/ericwq/goutmp" 18 ) 19 20 var strENOTTY = "inappropriate ioctl for device" 21 22 var ( 23 index int 24 utmpxMockData []*utmps.Utmpx 25 ) 26 27 type mockData struct { 28 xtype int 29 host string 30 line string 31 usr string 32 id int 33 pid int 34 } 35 36 func initData(data []mockData) { 37 utmpxMockData = make([]*utmps.Utmpx, 0) 38 for _, v := range data { 39 u := &utmps.Utmpx{} 40 u.SetType(v.xtype) 41 u.SetHost(v.host) 42 u.SetLine(v.line) 43 u.SetUser(v.usr) 44 u.SetId(v.id) 45 u.SetPid(v.pid) 46 utmpxMockData = append(utmpxMockData, u) 47 } 48 } 49 50 // return utmp mock data 51 func mockGetRecord() *utmps.Utmpx { 52 if 0 <= index && index < len(utmpxMockData) { 53 p := utmpxMockData[index] 54 index++ 55 return p 56 } 57 // fmt.Println("mockGetRecord return nil") 58 return nil 59 } 60 61 func TestWarnUnattached(t *testing.T) { 62 tc := []struct { 63 label string 64 ignoreHost string 65 count int 66 }{ 67 // 666 pts/0 exist, 888 pts/7 does not exist, only 666 remains 68 {"one match", "apshd:999", 1}, 69 // 666 pts0 exist, 999 pts/ptmx exist, so 666 and 999 remains 70 {"two match", "apshd:888", 2}, 71 } 72 73 data := []mockData{ 74 {utmps.USER_PROCESS, "apshd:777", "pts/1", "root", 3, 1}, 75 {utmps.USER_PROCESS, "apshd:888", "pts/7", getCurrentUser(), 7, 1221}, 76 {utmps.USER_PROCESS, "apshd:666", "pts/0", getCurrentUser(), 0, 1222}, 77 {utmps.USER_PROCESS, "192.168.0.123 via apshd:555", "pts/0", getCurrentUser(), 0, 1223}, 78 {utmps.USER_PROCESS, "apshd:999", "pts/ptmx", getCurrentUser(), 2, 1224}, 79 } 80 initData(data) 81 82 // open pts for test, 83 // on some container, if pts/0 is not opened, the test will failed. so we open it. 84 ptmx, pts, err := pty.Open() 85 if err != nil { 86 t.Errorf("test warnUnattached open pts failed, %s\n", err) 87 } 88 defer func() { 89 ptmx.Close() 90 pts.Close() 91 }() 92 93 for _, v := range tc { 94 t.Run(v.label, func(t *testing.T) { 95 var out strings.Builder 96 setGetRecord(mockGetRecord) 97 index = 0 98 defer func() { 99 setGetRecord(utmps.GetRecord) 100 }() 101 102 warnUnattached(&out, getCurrentUser(), v.ignoreHost) 103 104 got := out.String() 105 // t.Logf("%q\n", got) 106 count := strings.Count(got, "- ") 107 switch count { 108 case 0: // warnUnattached found one unattached session 109 if strings.Index(got, "a detached session on this server") != -1 && 110 v.count != 1 { 111 t.Errorf("#test warnUnattached() %q expect %d warning, got 1.\n", 112 v.label, v.count) 113 } 114 default: // warnUnattached found more than one unattached session 115 if count != v.count { 116 t.Errorf("#test warnUnattached() %q expect %d warning, got %d. \n%s\n", 117 v.label, v.count, count, got) 118 } 119 } 120 }) 121 } 122 } 123 124 func TestWarnUnattachedZero(t *testing.T) { 125 tc := []struct { 126 label string 127 ignoreHost string 128 count int 129 }{ 130 {"zero match", "apshd:888", 0}, 131 } 132 133 data := []mockData{ 134 {utmps.USER_PROCESS, "apshd:777", "pts/1", "root", 3, 1}, 135 {utmps.USER_PROCESS, "192.168.0.123 via apshd:888", "pts/8", getCurrentUser(), 7, 1221}, 136 {utmps.USER_PROCESS, "192.168.0.123 via apshd:666", "pts/0", getCurrentUser(), 0, 1222}, 137 {utmps.USER_PROCESS, "192.168.0.123 via apshd:555", "pts/9", getCurrentUser(), 0, 1223}, 138 {utmps.USER_PROCESS, "192.168.0.123 via apshd:999", "pts/ptmx", getCurrentUser(), 2, 1224}, 139 } 140 initData(data) 141 for _, v := range tc { 142 t.Run(v.label, func(t *testing.T) { 143 var out strings.Builder 144 setGetRecord(mockGetRecord) 145 index = 0 146 defer func() { 147 setGetRecord(utmps.GetRecord) 148 }() 149 150 warnUnattached(&out, getCurrentUser(), v.ignoreHost) 151 152 got := out.String() 153 // t.Logf("%q\n", got) 154 if len(got) != 0 { 155 t.Errorf("#test %q expect empty string, got %q\n", v.label, got) 156 } 157 }) 158 } 159 } 160 161 func TestBuildConfig(t *testing.T) { 162 tc := []struct { 163 label string 164 conf0 Config 165 conf2 Config 166 hint string 167 ok bool 168 }{ 169 { 170 "UTF-8 locale", 171 Config{ 172 version: false, server: false, verbose: 0, desiredIP: "", desiredPort: "", 173 locales: localeFlag{"LC_ALL": "en_US.UTF-8", "LANG": "en_US.UTF-8"}, 174 commandPath: "", commandArgv: []string{"/bin/sh", "-sh"}, withMotd: false, 175 }, 176 Config{ 177 version: false, server: false, verbose: 0, desiredIP: "", desiredPort: "", 178 locales: localeFlag{"LC_ALL": "en_US.UTF-8", "LANG": "en_US.UTF-8"}, 179 commandPath: "/bin/sh", commandArgv: []string{"-sh"}, withMotd: false, 180 }, 181 "", true, 182 }, 183 { 184 "empty commandArgv", 185 Config{ 186 version: false, server: false, verbose: 0, desiredIP: "", desiredPort: "", 187 locales: localeFlag{"LC_ALL": "en_US.UTF-8"}, 188 commandPath: "", commandArgv: []string{}, withMotd: false, 189 }, 190 Config{ 191 version: false, server: false, verbose: 0, desiredIP: "", desiredPort: "", 192 locales: localeFlag{"LC_ALL": "en_US.UTF-8"}, 193 commandPath: "/bin/sh", commandArgv: []string{"-sh"}, withMotd: true, 194 flowControl: _FC_DEF_BASH_SHELL, 195 }, 196 // macOS: /bin/zsh 197 // alpine: /bin/ash 198 "", true, 199 }, 200 // { 201 // "non UTF-8 locale", 202 // Config{ 203 // version: false, server: false, verbose: 0, desiredIP: "", desiredPort: "", 204 // locales: localeFlag{"LC_ALL": "zh_CN.GB2312", "LANG": "zh_CN.GB2312"}, 205 // commandPath: "", commandArgv: []string{"/bin/sh", "-sh"}, withMotd: false, 206 // }, // TODO GB2312 is not available in apline linux 207 // Config{ 208 // version: false, server: false, verbose: 0, desiredIP: "", desiredPort: "", 209 // locales: localeFlag{}, 210 // commandPath: "/bin/sh", commandArgv: []string{"*sh"}, withMotd: false, 211 // }, 212 // errors.New("UTF-8 locale fail."), 213 // }, 214 { 215 "commandArgv is one string", 216 Config{ 217 version: false, server: false, verbose: 0, desiredIP: "", desiredPort: "", 218 locales: localeFlag{"LC_ALL": "en_US.UTF-8", "LANG": "en_US.UTF-8"}, 219 commandPath: "", commandArgv: []string{"/bin/sh"}, withMotd: false, 220 }, 221 Config{ 222 version: false, server: false, verbose: 0, desiredIP: "", desiredPort: "", 223 locales: localeFlag{"LC_ALL": "en_US.UTF-8", "LANG": "en_US.UTF-8"}, 224 commandPath: "/bin/sh", commandArgv: []string{"-sh"}, withMotd: false, 225 }, 226 "", true, 227 }, 228 { 229 "missing SSH_CONNECTION", 230 Config{ 231 version: false, server: true, verbose: 0, desiredIP: "", desiredPort: "", 232 locales: localeFlag{"LC_ALL": "en_US.UTF-8", "LANG": "en_US.UTF-8"}, 233 commandPath: "", commandArgv: []string{"/bin/sh", "-sh"}, withMotd: false, 234 }, 235 Config{ 236 version: false, server: true, verbose: 0, desiredIP: "", desiredPort: "", 237 locales: localeFlag{"LC_ALL": "en_US.UTF-8", "LANG": "en_US.UTF-8"}, 238 commandPath: "", commandArgv: []string{"/bin/sh", "-sh"}, withMotd: false, 239 }, 240 "Warning: SSH_CONNECTION not found; binding to any interface.", false, 241 }, 242 } 243 244 for _, v := range tc { 245 t.Run(v.label, func(t *testing.T) { 246 247 // set SHELL for empty commandArgv 248 if len(v.conf0.commandArgv) == 0 { 249 shell := os.Getenv("SHELL") 250 defer os.Setenv("SHELL", shell) 251 os.Unsetenv("SHELL") 252 253 // getShell() will fail 254 defer func() { 255 v.conf0.flowControl = 0 256 }() 257 258 v.conf0.flowControl = _FC_DEF_BASH_SHELL 259 } 260 261 if v.conf0.server { // unset SSH_CONNECTION, getSSHip will return false 262 shell := os.Getenv("SSH_CONNECTION") 263 defer os.Setenv("SSH_CONNECTION", shell) 264 os.Unsetenv("SSH_CONNECTION") 265 } 266 267 // validate buildConfig 268 hint, ok := v.conf0.buildConfig() 269 v.conf0.serve = nil // disable the serve func for testing 270 271 if hint != v.hint || ok != v.ok { 272 t.Errorf("#test buildConfig got hint=%s, ok=%t, expect hint=%s, ok=%t\n", hint, ok, v.hint, v.ok) 273 } 274 if !reflect.DeepEqual(v.conf0, v.conf2) { 275 t.Errorf("#test buildConfig got \n%+v, expect \n%+v\n", v.conf0, v.conf2) 276 } 277 // reset the environment 278 util.ClearLocaleVariables() 279 280 // restore logW 281 // logW = log.New(os.Stdout, "WARN: ", log.Ldate|log.Ltime|log.Lshortfile) 282 }) 283 } 284 }