system/output: implement MessageError
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Test / Hakurei (push) Successful in 43s
				
			
		
			
				
	
				Test / Create distribution (push) Successful in 26s
				
			
		
			
				
	
				Test / Sandbox (push) Successful in 1m40s
				
			
		
			
				
	
				Test / Hpkg (push) Successful in 3m35s
				
			
		
			
				
	
				Test / Sandbox (race detector) (push) Successful in 4m24s
				
			
		
			
				
	
				Test / Hakurei (race detector) (push) Successful in 5m20s
				
			
		
			
				
	
				Test / Flake checks (push) Successful in 1m37s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Test / Hakurei (push) Successful in 43s
				
			Test / Create distribution (push) Successful in 26s
				
			Test / Sandbox (push) Successful in 1m40s
				
			Test / Hpkg (push) Successful in 3m35s
				
			Test / Sandbox (race detector) (push) Successful in 4m24s
				
			Test / Hakurei (race detector) (push) Successful in 5m20s
				
			Test / Flake checks (push) Successful in 1m37s
				
			This error is also formatted differently based on state. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
		
							parent
							
								
									780e3e5465
								
							
						
					
					
						commit
						b489a3bba1
					
				| @ -22,14 +22,14 @@ func SetOutput(v container.Msg) { | |||||||
| type OpError struct { | type OpError struct { | ||||||
| 	Op     string | 	Op     string | ||||||
| 	Err    error | 	Err    error | ||||||
| 	Message string | 	Msg    string | ||||||
| 	Revert bool | 	Revert bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (e *OpError) Unwrap() error { return e.Err } | func (e *OpError) Unwrap() error { return e.Err } | ||||||
| func (e *OpError) Error() string { | func (e *OpError) Error() string { | ||||||
| 	if e.Message != "" { | 	if e.Msg != "" { | ||||||
| 		return e.Message | 		return e.Msg | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	switch { | 	switch { | ||||||
| @ -47,6 +47,16 @@ func (e *OpError) Error() string { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (e *OpError) Message() string { | ||||||
|  | 	switch { | ||||||
|  | 	case e.Msg != "": | ||||||
|  | 		return e.Error() | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		return "cannot " + e.Error() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // newOpError returns an [OpError] without a message string. | // newOpError returns an [OpError] without a message string. | ||||||
| func newOpError(op string, err error, revert bool) error { | func newOpError(op string, err error, revert bool) error { | ||||||
| 	if err == nil { | 	if err == nil { | ||||||
| @ -69,10 +79,18 @@ func printJoinedError(println func(v ...any), fallback string, err error) { | |||||||
| 		error | 		error | ||||||
| 	} | 	} | ||||||
| 	if !errors.As(err, &joinErr) { | 	if !errors.As(err, &joinErr) { | ||||||
|  | 		if m, ok := container.GetErrorMessage(err); ok { | ||||||
|  | 			println(m) | ||||||
|  | 		} else { | ||||||
| 			println(fallback, err) | 			println(fallback, err) | ||||||
|  | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		for _, err = range joinErr.Unwrap() { | 		for _, err = range joinErr.Unwrap() { | ||||||
|  | 			if m, ok := container.GetErrorMessage(err); ok { | ||||||
|  | 				println(m) | ||||||
|  | 			} else { | ||||||
| 				println(err.Error()) | 				println(err.Error()) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -19,27 +19,33 @@ func TestOpError(t *testing.T) { | |||||||
| 		s    string | 		s    string | ||||||
| 		is   error | 		is   error | ||||||
| 		isF  error | 		isF  error | ||||||
|  | 		msg  string | ||||||
| 	}{ | 	}{ | ||||||
| 		{"message", newOpErrorMessage("dbus", ErrDBusConfig, | 		{"message", newOpErrorMessage("dbus", ErrDBusConfig, | ||||||
| 			"attempted to create message bus proxy args without session bus config", false), | 			"attempted to create message bus proxy args without session bus config", false), | ||||||
| 			"attempted to create message bus proxy args without session bus config", | 			"attempted to create message bus proxy args without session bus config", | ||||||
| 			ErrDBusConfig, syscall.ENOTRECOVERABLE}, | 			ErrDBusConfig, syscall.ENOTRECOVERABLE, | ||||||
|  | 			"attempted to create message bus proxy args without session bus config"}, | ||||||
| 
 | 
 | ||||||
| 		{"apply", newOpError("tmpfile", syscall.EBADE, false), | 		{"apply", newOpError("tmpfile", syscall.EBADE, false), | ||||||
| 			"apply tmpfile: invalid exchange", | 			"apply tmpfile: invalid exchange", | ||||||
| 			syscall.EBADE, syscall.EBADF}, | 			syscall.EBADE, syscall.EBADF, | ||||||
|  | 			"cannot apply tmpfile: invalid exchange"}, | ||||||
| 
 | 
 | ||||||
| 		{"revert", newOpError("wayland", syscall.EBADF, true), | 		{"revert", newOpError("wayland", syscall.EBADF, true), | ||||||
| 			"revert wayland: bad file descriptor", | 			"revert wayland: bad file descriptor", | ||||||
| 			syscall.EBADF, syscall.EBADE}, | 			syscall.EBADF, syscall.EBADE, | ||||||
|  | 			"cannot revert wayland: bad file descriptor"}, | ||||||
| 
 | 
 | ||||||
| 		{"path", newOpError("tmpfile", &os.PathError{Op: "stat", Path: "/run/dbus", Err: syscall.EISDIR}, false), | 		{"path", newOpError("tmpfile", &os.PathError{Op: "stat", Path: "/run/dbus", Err: syscall.EISDIR}, false), | ||||||
| 			"stat /run/dbus: is a directory", | 			"stat /run/dbus: is a directory", | ||||||
| 			syscall.EISDIR, syscall.ENOTDIR}, | 			syscall.EISDIR, syscall.ENOTDIR, | ||||||
|  | 			"cannot stat /run/dbus: is a directory"}, | ||||||
| 
 | 
 | ||||||
| 		{"net", newOpError("wayland", &net.OpError{Op: "dial", Net: "unix", Addr: &net.UnixAddr{Name: "/run/user/1000/wayland-1", Net: "unix"}, Err: syscall.ENOENT}, false), | 		{"net", newOpError("wayland", &net.OpError{Op: "dial", Net: "unix", Addr: &net.UnixAddr{Name: "/run/user/1000/wayland-1", Net: "unix"}, Err: syscall.ENOENT}, false), | ||||||
| 			"dial unix /run/user/1000/wayland-1: no such file or directory", | 			"dial unix /run/user/1000/wayland-1: no such file or directory", | ||||||
| 			syscall.ENOENT, syscall.EPERM}, | 			syscall.ENOENT, syscall.EPERM, | ||||||
|  | 			"cannot dial unix /run/user/1000/wayland-1: no such file or directory"}, | ||||||
| 	} | 	} | ||||||
| 	for _, tc := range testCases { | 	for _, tc := range testCases { | ||||||
| 		t.Run(tc.name, func(t *testing.T) { | 		t.Run(tc.name, func(t *testing.T) { | ||||||
| @ -48,6 +54,7 @@ func TestOpError(t *testing.T) { | |||||||
| 					t.Errorf("Error: %q, want %q", got, tc.s) | 					t.Errorf("Error: %q, want %q", got, tc.s) | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
|  | 
 | ||||||
| 			t.Run("is", func(t *testing.T) { | 			t.Run("is", func(t *testing.T) { | ||||||
| 				if !errors.Is(tc.err, tc.is) { | 				if !errors.Is(tc.err, tc.is) { | ||||||
| 					t.Error("Is: unexpected false") | 					t.Error("Is: unexpected false") | ||||||
| @ -56,6 +63,17 @@ func TestOpError(t *testing.T) { | |||||||
| 					t.Error("Is: unexpected true") | 					t.Error("Is: unexpected true") | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
|  | 
 | ||||||
|  | 			t.Run("msg", func(t *testing.T) { | ||||||
|  | 				if got, ok := container.GetErrorMessage(tc.err); !ok { | ||||||
|  | 					if tc.msg != "" { | ||||||
|  | 						t.Errorf("GetErrorMessage: err does not implement MessageError") | ||||||
|  | 					} | ||||||
|  | 					return | ||||||
|  | 				} else if got != tc.msg { | ||||||
|  | 					t.Errorf("GetErrorMessage: %q, want %q", got, tc.msg) | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -103,14 +121,40 @@ func TestPrintJoinedError(t *testing.T) { | |||||||
| 		want [][]any | 		want [][]any | ||||||
| 	}{ | 	}{ | ||||||
| 		{"nil", nil, [][]any{{"not a joined error:", nil}}}, | 		{"nil", nil, [][]any{{"not a joined error:", nil}}}, | ||||||
| 		{"unwrapped", syscall.EINVAL, [][]any{{"not a joined error:", syscall.EINVAL}}}, |  | ||||||
| 		{"single", errors.Join(syscall.EINVAL), [][]any{{"invalid argument"}}}, | 		{"single", errors.Join(syscall.EINVAL), [][]any{{"invalid argument"}}}, | ||||||
| 
 | 
 | ||||||
|  | 		{"unwrapped", syscall.EINVAL, [][]any{{"not a joined error:", syscall.EINVAL}}}, | ||||||
|  | 		{"unwrapped message", &OpError{ | ||||||
|  | 			Op:  "meow", | ||||||
|  | 			Err: syscall.EBADFD, | ||||||
|  | 		}, [][]any{ | ||||||
|  | 			{"cannot apply meow: file descriptor in bad state"}, | ||||||
|  | 		}}, | ||||||
|  | 
 | ||||||
| 		{"many", errors.Join(syscall.ENOTRECOVERABLE, syscall.ETIMEDOUT, syscall.EBADFD), [][]any{ | 		{"many", errors.Join(syscall.ENOTRECOVERABLE, syscall.ETIMEDOUT, syscall.EBADFD), [][]any{ | ||||||
| 			{"state not recoverable"}, | 			{"state not recoverable"}, | ||||||
| 			{"connection timed out"}, | 			{"connection timed out"}, | ||||||
| 			{"file descriptor in bad state"}, | 			{"file descriptor in bad state"}, | ||||||
| 		}}, | 		}}, | ||||||
|  | 		{"many message", errors.Join( | ||||||
|  | 			&container.StartError{ | ||||||
|  | 				Step: "meow", | ||||||
|  | 				Err:  syscall.ENOMEM, | ||||||
|  | 			}, | ||||||
|  | 			&os.PathError{ | ||||||
|  | 				Op:   "meow", | ||||||
|  | 				Path: "/proc/nonexistent", | ||||||
|  | 				Err:  syscall.ENOSYS, | ||||||
|  | 			}, | ||||||
|  | 			&OpError{ | ||||||
|  | 				Op:     "meow", | ||||||
|  | 				Err:    syscall.ENODEV, | ||||||
|  | 				Revert: true, | ||||||
|  | 			}), [][]any{ | ||||||
|  | 			{"cannot meow: cannot allocate memory"}, | ||||||
|  | 			{"meow /proc/nonexistent: function not implemented"}, | ||||||
|  | 			{"cannot revert meow: no such device"}, | ||||||
|  | 		}}, | ||||||
| 	} | 	} | ||||||
| 	for _, tc := range testCases { | 	for _, tc := range testCases { | ||||||
| 		t.Run(tc.name, func(t *testing.T) { | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user