etchosts_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. package etchosts
  2. import (
  3. "bytes"
  4. "fmt"
  5. "os"
  6. "testing"
  7. "golang.org/x/sync/errgroup"
  8. )
  9. func TestBuildDefault(t *testing.T) {
  10. file, err := os.CreateTemp("", "")
  11. if err != nil {
  12. t.Fatal(err)
  13. }
  14. defer os.Remove(file.Name())
  15. // check that /etc/hosts has consistent ordering
  16. for i := 0; i <= 5; i++ {
  17. err = Build(file.Name(), "", "", "", nil)
  18. if err != nil {
  19. t.Fatal(err)
  20. }
  21. content, err := os.ReadFile(file.Name())
  22. if err != nil {
  23. t.Fatal(err)
  24. }
  25. expected := "127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n"
  26. if expected != string(content) {
  27. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  28. }
  29. }
  30. }
  31. func TestBuildHostnameDomainname(t *testing.T) {
  32. file, err := os.CreateTemp("", "")
  33. if err != nil {
  34. t.Fatal(err)
  35. }
  36. defer os.Remove(file.Name())
  37. err = Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil)
  38. if err != nil {
  39. t.Fatal(err)
  40. }
  41. content, err := os.ReadFile(file.Name())
  42. if err != nil {
  43. t.Fatal(err)
  44. }
  45. if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
  46. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  47. }
  48. }
  49. func TestBuildHostname(t *testing.T) {
  50. file, err := os.CreateTemp("", "")
  51. if err != nil {
  52. t.Fatal(err)
  53. }
  54. defer os.Remove(file.Name())
  55. err = Build(file.Name(), "10.11.12.13", "testhostname", "", nil)
  56. if err != nil {
  57. t.Fatal(err)
  58. }
  59. content, err := os.ReadFile(file.Name())
  60. if err != nil {
  61. t.Fatal(err)
  62. }
  63. if expected := "10.11.12.13\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) {
  64. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  65. }
  66. }
  67. func TestBuildHostnameFQDN(t *testing.T) {
  68. file, err := os.CreateTemp("", "")
  69. if err != nil {
  70. t.Fatal(err)
  71. }
  72. defer os.Remove(file.Name())
  73. err = Build(file.Name(), "10.11.12.13", "testhostname.testdomainname.com", "", nil)
  74. if err != nil {
  75. t.Fatal(err)
  76. }
  77. content, err := os.ReadFile(file.Name())
  78. if err != nil {
  79. t.Fatal(err)
  80. }
  81. if expected := "10.11.12.13\ttesthostname.testdomainname.com testhostname\n"; !bytes.Contains(content, []byte(expected)) {
  82. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  83. }
  84. }
  85. func TestBuildNoIP(t *testing.T) {
  86. file, err := os.CreateTemp("", "")
  87. if err != nil {
  88. t.Fatal(err)
  89. }
  90. defer os.Remove(file.Name())
  91. err = Build(file.Name(), "", "testhostname", "", nil)
  92. if err != nil {
  93. t.Fatal(err)
  94. }
  95. content, err := os.ReadFile(file.Name())
  96. if err != nil {
  97. t.Fatal(err)
  98. }
  99. if expected := ""; !bytes.Contains(content, []byte(expected)) {
  100. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  101. }
  102. }
  103. func TestUpdate(t *testing.T) {
  104. file, err := os.CreateTemp("", "")
  105. if err != nil {
  106. t.Fatal(err)
  107. }
  108. defer os.Remove(file.Name())
  109. if err := Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil); err != nil {
  110. t.Fatal(err)
  111. }
  112. content, err := os.ReadFile(file.Name())
  113. if err != nil {
  114. t.Fatal(err)
  115. }
  116. if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
  117. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  118. }
  119. if err := Update(file.Name(), "1.1.1.1", "testhostname"); err != nil {
  120. t.Fatal(err)
  121. }
  122. content, err = os.ReadFile(file.Name())
  123. if err != nil {
  124. t.Fatal(err)
  125. }
  126. if expected := "1.1.1.1\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
  127. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  128. }
  129. }
  130. // This regression test ensures that when a host is given a new IP
  131. // via the Update function that other hosts which start with the
  132. // same name as the targeted host are not erroneously updated as well.
  133. // In the test example, if updating a host called "prefix", unrelated
  134. // hosts named "prefixAndMore" or "prefix2" or anything else starting
  135. // with "prefix" should not be changed. For more information see
  136. // GitHub issue #603.
  137. func TestUpdateIgnoresPrefixedHostname(t *testing.T) {
  138. file, err := os.CreateTemp("", "")
  139. if err != nil {
  140. t.Fatal(err)
  141. }
  142. defer os.Remove(file.Name())
  143. if err := Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", []Record{
  144. {
  145. Hosts: "prefix",
  146. IP: "2.2.2.2",
  147. },
  148. {
  149. Hosts: "prefixAndMore",
  150. IP: "3.3.3.3",
  151. },
  152. {
  153. Hosts: "unaffectedHost",
  154. IP: "4.4.4.4",
  155. },
  156. }); err != nil {
  157. t.Fatal(err)
  158. }
  159. content, err := os.ReadFile(file.Name())
  160. if err != nil {
  161. t.Fatal(err)
  162. }
  163. if expected := "2.2.2.2\tprefix\n3.3.3.3\tprefixAndMore\n4.4.4.4\tunaffectedHost\n"; !bytes.Contains(content, []byte(expected)) {
  164. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  165. }
  166. if err := Update(file.Name(), "5.5.5.5", "prefix"); err != nil {
  167. t.Fatal(err)
  168. }
  169. content, err = os.ReadFile(file.Name())
  170. if err != nil {
  171. t.Fatal(err)
  172. }
  173. if expected := "5.5.5.5\tprefix\n3.3.3.3\tprefixAndMore\n4.4.4.4\tunaffectedHost\n"; !bytes.Contains(content, []byte(expected)) {
  174. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  175. }
  176. }
  177. // This regression test covers the host prefix issue for the
  178. // Delete function. In the test example, if deleting a host called
  179. // "prefix", an unrelated host called "prefixAndMore" should not
  180. // be deleted. For more information see GitHub issue #603.
  181. func TestDeleteIgnoresPrefixedHostname(t *testing.T) {
  182. file, err := os.CreateTemp("", "")
  183. if err != nil {
  184. t.Fatal(err)
  185. }
  186. defer os.Remove(file.Name())
  187. err = Build(file.Name(), "", "", "", nil)
  188. if err != nil {
  189. t.Fatal(err)
  190. }
  191. if err := Add(file.Name(), []Record{
  192. {
  193. Hosts: "prefix",
  194. IP: "1.1.1.1",
  195. },
  196. {
  197. Hosts: "prefixAndMore",
  198. IP: "2.2.2.2",
  199. },
  200. }); err != nil {
  201. t.Fatal(err)
  202. }
  203. if err := Delete(file.Name(), []Record{
  204. {
  205. Hosts: "prefix",
  206. IP: "1.1.1.1",
  207. },
  208. }); err != nil {
  209. t.Fatal(err)
  210. }
  211. content, err := os.ReadFile(file.Name())
  212. if err != nil {
  213. t.Fatal(err)
  214. }
  215. if expected := "2.2.2.2\tprefixAndMore\n"; !bytes.Contains(content, []byte(expected)) {
  216. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  217. }
  218. if expected := "1.1.1.1\tprefix\n"; bytes.Contains(content, []byte(expected)) {
  219. t.Fatalf("Did not expect to find '%s' got '%s'", expected, content)
  220. }
  221. }
  222. func TestAddEmpty(t *testing.T) {
  223. file, err := os.CreateTemp("", "")
  224. if err != nil {
  225. t.Fatal(err)
  226. }
  227. defer os.Remove(file.Name())
  228. err = Build(file.Name(), "", "", "", nil)
  229. if err != nil {
  230. t.Fatal(err)
  231. }
  232. if err := Add(file.Name(), []Record{}); err != nil {
  233. t.Fatal(err)
  234. }
  235. }
  236. func TestAdd(t *testing.T) {
  237. file, err := os.CreateTemp("", "")
  238. if err != nil {
  239. t.Fatal(err)
  240. }
  241. defer os.Remove(file.Name())
  242. err = Build(file.Name(), "", "", "", nil)
  243. if err != nil {
  244. t.Fatal(err)
  245. }
  246. if err := Add(file.Name(), []Record{
  247. {
  248. Hosts: "testhostname",
  249. IP: "2.2.2.2",
  250. },
  251. }); err != nil {
  252. t.Fatal(err)
  253. }
  254. content, err := os.ReadFile(file.Name())
  255. if err != nil {
  256. t.Fatal(err)
  257. }
  258. if expected := "2.2.2.2\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) {
  259. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  260. }
  261. }
  262. func TestDeleteEmpty(t *testing.T) {
  263. file, err := os.CreateTemp("", "")
  264. if err != nil {
  265. t.Fatal(err)
  266. }
  267. defer os.Remove(file.Name())
  268. err = Build(file.Name(), "", "", "", nil)
  269. if err != nil {
  270. t.Fatal(err)
  271. }
  272. if err := Delete(file.Name(), []Record{}); err != nil {
  273. t.Fatal(err)
  274. }
  275. }
  276. func TestDeleteNewline(t *testing.T) {
  277. file, err := os.CreateTemp("", "")
  278. if err != nil {
  279. t.Fatal(err)
  280. }
  281. defer os.Remove(file.Name())
  282. b := []byte("\n")
  283. if _, err := file.Write(b); err != nil {
  284. t.Fatal(err)
  285. }
  286. rec := []Record{
  287. {
  288. Hosts: "prefix",
  289. IP: "2.2.2.2",
  290. },
  291. }
  292. if err := Delete(file.Name(), rec); err != nil {
  293. t.Fatal(err)
  294. }
  295. }
  296. func TestDelete(t *testing.T) {
  297. file, err := os.CreateTemp("", "")
  298. if err != nil {
  299. t.Fatal(err)
  300. }
  301. defer os.Remove(file.Name())
  302. err = Build(file.Name(), "", "", "", nil)
  303. if err != nil {
  304. t.Fatal(err)
  305. }
  306. if err := Add(file.Name(), []Record{
  307. {
  308. Hosts: "testhostname1",
  309. IP: "1.1.1.1",
  310. },
  311. {
  312. Hosts: "testhostname2",
  313. IP: "2.2.2.2",
  314. },
  315. {
  316. Hosts: "testhostname3",
  317. IP: "3.3.3.3",
  318. },
  319. }); err != nil {
  320. t.Fatal(err)
  321. }
  322. if err := Delete(file.Name(), []Record{
  323. {
  324. Hosts: "testhostname1",
  325. IP: "1.1.1.1",
  326. },
  327. {
  328. Hosts: "testhostname3",
  329. IP: "3.3.3.3",
  330. },
  331. }); err != nil {
  332. t.Fatal(err)
  333. }
  334. content, err := os.ReadFile(file.Name())
  335. if err != nil {
  336. t.Fatal(err)
  337. }
  338. if expected := "2.2.2.2\ttesthostname2\n"; !bytes.Contains(content, []byte(expected)) {
  339. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  340. }
  341. if expected := "1.1.1.1\ttesthostname1\n"; bytes.Contains(content, []byte(expected)) {
  342. t.Fatalf("Did not expect to find '%s' got '%s'", expected, content)
  343. }
  344. }
  345. func TestConcurrentWrites(t *testing.T) {
  346. file, err := os.CreateTemp("", "")
  347. if err != nil {
  348. t.Fatal(err)
  349. }
  350. defer os.Remove(file.Name())
  351. err = Build(file.Name(), "", "", "", nil)
  352. if err != nil {
  353. t.Fatal(err)
  354. }
  355. if err := Add(file.Name(), []Record{
  356. {
  357. Hosts: "inithostname",
  358. IP: "172.17.0.1",
  359. },
  360. }); err != nil {
  361. t.Fatal(err)
  362. }
  363. group := new(errgroup.Group)
  364. for i := 0; i < 10; i++ {
  365. i := i
  366. group.Go(func() error {
  367. rec := []Record{
  368. {
  369. IP: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i),
  370. Hosts: fmt.Sprintf("testhostname%d", i),
  371. },
  372. }
  373. for j := 0; j < 25; j++ {
  374. if err := Add(file.Name(), rec); err != nil {
  375. return err
  376. }
  377. if err := Delete(file.Name(), rec); err != nil {
  378. return err
  379. }
  380. }
  381. return nil
  382. })
  383. }
  384. if err := group.Wait(); err != nil {
  385. t.Fatal(err)
  386. }
  387. content, err := os.ReadFile(file.Name())
  388. if err != nil {
  389. t.Fatal(err)
  390. }
  391. if expected := "172.17.0.1\tinithostname\n"; !bytes.Contains(content, []byte(expected)) {
  392. t.Fatalf("Expected to find '%s' got '%s'", expected, content)
  393. }
  394. }
  395. func benchDelete(b *testing.B) {
  396. b.StopTimer()
  397. file, err := os.CreateTemp("", "")
  398. if err != nil {
  399. b.Fatal(err)
  400. }
  401. defer func() {
  402. b.StopTimer()
  403. file.Close()
  404. os.Remove(file.Name())
  405. b.StartTimer()
  406. }()
  407. err = Build(file.Name(), "", "", "", nil)
  408. if err != nil {
  409. b.Fatal(err)
  410. }
  411. var records []Record
  412. var toDelete []Record
  413. for i := 0; i < 255; i++ {
  414. record := Record{
  415. Hosts: fmt.Sprintf("testhostname%d", i),
  416. IP: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i),
  417. }
  418. records = append(records, record)
  419. if i%2 == 0 {
  420. toDelete = append(records, record)
  421. }
  422. }
  423. if err := Add(file.Name(), records); err != nil {
  424. b.Fatal(err)
  425. }
  426. b.StartTimer()
  427. if err := Delete(file.Name(), toDelete); err != nil {
  428. b.Fatal(err)
  429. }
  430. }
  431. func BenchmarkDelete(b *testing.B) {
  432. for i := 0; i < b.N; i++ {
  433. benchDelete(b)
  434. }
  435. }