This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch main
in repository ego.

View the commit online.

commit fdd4ec00b57457f6e4e612c7c5f2c582a3985072
Author: [email protected] <[email protected]>
AuthorDate: Sat Mar 28 12:36:31 2026 -0600

    test: add hello world e2e test to verify buffer rendering
    
    Add TestHelloWorldGolden to demonstrate that the test harness can render
    actual visible content (not just uninitialised buffers). Improves the harness
    by forcing ELM_ENGINE=buffer for headless testing, extracting renderEE() to
    deduplicate pixel unpacking logic, and adding RenderWindow() to render
    Elementary windows directly with automatic geometry detection.
    
    Includes golden image baseline showing rendered "Hello World" text.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 tests/harness/harness.go                          |  61 ++++++++++++++++++++--
 tests/integration/integration_test.go             |  61 ++++++++++++++++++++++
 tests/integration/testdata/hello_world_120x60.png | Bin 0 -> 620 bytes
 3 files changed, 119 insertions(+), 3 deletions(-)

diff --git a/tests/harness/harness.go b/tests/harness/harness.go
index c1e9b22..bc07292 100644
--- a/tests/harness/harness.go
+++ b/tests/harness/harness.go
@@ -9,11 +9,13 @@ package harness
 
 #include <Ecore_Evas.h>
 #include <Evas.h>
+#include <Elementary.h>
 */
 import "C"
 
 import (
 	"image"
+	"os"
 	"testing"
 	"unsafe"
 
@@ -30,6 +32,9 @@ import (
 // If setup is non-nil it is called directly after Init (the calling goroutine
 // is the EFL thread).
 func Run(m *testing.M, setup func()) int {
+	// Force all Elementary windows to use the buffer engine so tests run
+	// headless without requiring a display server.
+	os.Setenv("ELM_ENGINE", "buffer")
 	efl.Init()
 
 	if setup != nil {
@@ -94,10 +99,17 @@ func (bw *BufferWindow) EcoreEvas() unsafe.Pointer {
 // Must be called on the EFL thread.
 func (bw *BufferWindow) Render() image.Image {
 	C.ecore_evas_manual_render(bw.ee)
+	return renderEE(bw.ee, bw.w, bw.h)
+}
 
-	pixels := C.ecore_evas_buffer_pixels_get(bw.ee)
+// renderEE reads back the pixel buffer from ee (which must already have been
+// manually rendered by the caller), un-premultiplies the ARGB32 data, and
+// returns it as an *image.NRGBA of size w×h. A nil pixels pointer returns an
+// empty image rather than panicking.
+func renderEE(ee *C.Ecore_Evas, w, h int) *image.NRGBA {
+	pixels := C.ecore_evas_buffer_pixels_get(ee)
 
-	img := image.NewNRGBA(image.Rect(0, 0, bw.w, bw.h))
+	img := image.NewNRGBA(image.Rect(0, 0, w, h))
 
 	if pixels == nil {
 		return img
@@ -105,7 +117,7 @@ func (bw *BufferWindow) Render() image.Image {
 
 	// Reinterpret the pixel buffer as a slice of uint32 values. Each uint32
 	// holds one ARGB32 pixel with premultiplied alpha in host byte order.
-	n := bw.w * bw.h
+	n := w * h
 	src := unsafe.Slice((*uint32)(pixels), n)
 
 	for i, px := range src {
@@ -157,3 +169,46 @@ func (bw *BufferWindow) Free() {
 	C.ecore_evas_free(bw.ee)
 	bw.ee = nil
 }
+
+// RenderWindow forces a synchronous render of an Elementary window and returns
+// the resulting pixel data as an image.NRGBA. It obtains the underlying
+// Ecore_Evas from the window's Evas canvas using the standard EFL chain:
+//
+//	Eo* -> Evas* (via evas_object_evas_get)
+//	     -> Ecore_Evas* (via ecore_evas_ecore_evas_get)
+//
+// The window geometry is queried directly from EFL via ecore_evas_geometry_get
+// so callers do not need to pass dimensions explicitly.
+//
+// The function then calls ecore_evas_manual_render and reads back pixels via
+// ecore_evas_buffer_pixels_get. Source pixels are ARGB32 with premultiplied
+// alpha; they are un-premultiplied before being stored in the returned image.
+//
+// On any nil in the CGo pointer chain a 0×0 empty image is returned so that
+// the test fails cleanly at assertion time rather than with a nil-pointer panic.
+//
+// Must be called on the EFL thread (inside efl.Sync or efl.Post).
+func RenderWindow(obj efl.Objecter) image.Image {
+	if obj == nil || obj.Eo() == nil {
+		return image.NewNRGBA(image.Rectangle{})
+	}
+
+	evas := C.evas_object_evas_get((*C.Eo)(obj.Eo()))
+	if evas == nil {
+		return image.NewNRGBA(image.Rectangle{})
+	}
+
+	ee := C.ecore_evas_ecore_evas_get(evas)
+	if ee == nil {
+		return image.NewNRGBA(image.Rectangle{})
+	}
+
+	// Query the actual canvas geometry so the caller does not need to supply
+	// dimensions; this also handles windows that were resized after creation.
+	var cw, ch C.int
+	C.ecore_evas_geometry_get(ee, nil, nil, &cw, &ch)
+	w, h := int(cw), int(ch)
+
+	C.ecore_evas_manual_render(ee)
+	return renderEE(ee, w, h)
+}
diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go
index 4b2e96b..f314791 100644
--- a/tests/integration/integration_test.go
+++ b/tests/integration/integration_test.go
@@ -46,6 +46,67 @@ func TestBufferRenderGolden(t *testing.T) {
 	assert.Golden(t, img, "empty_buffer_64x64")
 }
 
+// TestHelloWorldGolden verifies that the test harness can render actual visible
+// content. It creates an Elementary window backed by the buffer engine, places
+// a Textbox with "Hello World" text inside it, renders the window, and asserts
+// that the result is not an all-black image, proving that the harness produces
+// real output rather than an uninitialised buffer.
+func TestHelloWorldGolden(t *testing.T) {
+	const w, h = 120, 60
+
+	// Create the window on the EFL thread; require outside so t.FailNow does
+	// not kill the EFL goroutine.
+	var win *ui.Win
+	efl.Sync(func() {
+		win = ui.NewWin(nil)
+		win.SetSize(efl.Size2D{W: w, H: h})
+		win.SetVisible(true)
+	})
+	require.NotNil(t, win, "ui.Win should be created successfully")
+	defer efl.Sync(func() { win.Unref() })
+
+	// Create the textbox on the EFL thread; again require outside the sync.
+	var tb *ui.Textbox
+	efl.Sync(func() {
+		tb = ui.NewTextbox(win)
+		tb.SetText("Hello World")
+		// Size the textbox to fill the window so the text occupies a region
+		// large enough for pixels to differ from the background colour.
+		tb.SetGeometry(efl.Rect{X: 0, Y: 0, W: w, H: h})
+		tb.SetVisible(true)
+	})
+	require.NotNil(t, tb, "ui.Textbox should be created successfully")
+	defer efl.Sync(func() { tb.Unref() })
+
+	var img image.Image
+	efl.Sync(func() {
+		img = harness.RenderWindow(win)
+	})
+	require.NotNil(t, img, "render should return an image")
+
+	// Verify the image is not all black — at least some pixels must differ from
+	// pure black, proving the harness renders actual content.
+	allBlack := true
+	bounds := img.Bounds()
+outer:
+	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+		for x := bounds.Min.X; x < bounds.Max.X; x++ {
+			r, g, b, _ := img.At(x, y).RGBA()
+			if r != 0 || g != 0 || b != 0 {
+				allBlack = false
+				break outer
+			}
+		}
+	}
+	assert.Equal(t, false, allBlack, "hello world image should not be all black")
+
+	// Allow a small per-channel tolerance to absorb font rendering variation
+	// across different systems and library versions.
+	assert.Tolerance = 2
+	defer func() { assert.Tolerance = 0 }()
+	assert.Golden(t, img, "hello_world_120x60")
+}
+
 // TestButtonCreation verifies that the generated EFL bindings can create a
 // Efl.Ui.Win and a child Efl.Ui.Button end-to-end. All EFL object operations
 // must run on the EFL thread inside efl.Sync.
diff --git a/tests/integration/testdata/hello_world_120x60.png b/tests/integration/testdata/hello_world_120x60.png
new file mode 100644
index 0000000..e64476a
Binary files /dev/null and b/tests/integration/testdata/hello_world_120x60.png differ

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to