summaryrefslogtreecommitdiff
path: root/chapter31/src/test/java/com/seaofnodes/simple/AudioTest.java
blob: db8542d0b86819d94b8c61518aeea8e112697f47 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.seaofnodes.simple;

import com.seaofnodes.simple.util.Ary;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;

// Loader and Simple/C ABI checks: no miniaudio download or audio device.
public class AudioTest {
    private static String missing, playback;

    @BeforeClass public static void compilePrograms() throws Exception {
        var paths=new Ary<>(new String[]{TestC.RELEASE_SYS_DIR});
        missing=TestC.compile("""
            val main = { ->
                if( sys.audio.open()!=sys.audio.ERROR ) return 1;
                if( sys.audio.boop()!=sys.audio.ERROR || sys.audio.playing() ) return 2;
                sys.audio.stop(); sys.audio.close(); sys.audio.close();
                sys.io.p("silent fallback\\n");
                return 0;
            };
            ""","audio_missing",paths,TestC.CALL_CONVENTION,null,null,-1);
        playback=TestC.compile("""
            val main = { ->
                if( sys.audio.open()!=sys.audio.OK ) return 1;
                val pcm = new i16[3];
                pcm[0]=-32768; pcm[1]=123; pcm[2]=32767;
                int sound = sys.audio.load(pcm);
                if( sound!=4 || sys.audio.play(sound)!=0 || !sys.audio.playing() ) return 2;
                if( sys.audio.boop()!=0 || sys.audio.win()!=0 || sys.audio.lose()!=0 ) return 3;
                sys.audio.stop();
                if( sys.audio.playing() ) return 4;
                sys.audio.close();
                return 0;
            };
            ""","audio_playback",paths,TestC.CALL_CONVENTION,null,null,-1);
    }

    private static String run(String exe, String backend) throws Exception {
        ProcessBuilder pb=new ProcessBuilder(exe).redirectErrorStream(true);
        if( backend==null ) pb.environment().remove("SIMPLE_MINIAUDIO");
        else pb.environment().put("SIMPLE_MINIAUDIO",backend);
        Process child=pb.start();
        try {
            assertTrue("audio program timed out",child.waitFor(10,TimeUnit.SECONDS));
            String out=new String(child.getInputStream().readAllBytes(),StandardCharsets.UTF_8);
            assertEquals(out,0,child.exitValue());
            return out;
        } finally { if( child.isAlive() ) child.destroyForcibly(); }
    }

    @Test public void silentFallback() throws Exception {
        assertEquals("silent fallback\n",run(missing,null));
        assertEquals("silent fallback\n",run(missing,"off"));
        assertTrue(run(missing,"no-such-miniaudio-backend").contains("cannot load backend"));
    }

    @Test public void nativeBoundary() throws Exception {
        Path source=Path.of("build/objs/audio_stub.c");
        Path library=Path.of("build/objs/audio_stub"+(TestC.OS.startsWith("Windows") ? ".dll" : ".so"));
        Files.writeString(source,"""
            #include "simple_audio.h"
            static int active;
            static int32_t open_audio(void) { return 0; }
            static int32_t load(const int16_t *p, uint32_t n) {
                return n==3 && p[0]==-32768 && p[1]==123 && p[2]==32767 ? 4 : -1;
            }
            static int32_t play(int32_t cue) { active=1; return cue>=1 && cue<=4 ? 0 : -1; }
            static int32_t stop(void) { active=0; return 0; }
            static int32_t playing(void) { return active; }
            static void close_audio(void) { active=0; }
            static const struct simple_audio_api api={1,open_audio,load,play,stop,playing,close_audio};
            const struct simple_audio_api *simple_miniaudio_api(void) { return &api; }
            """);
        TestC.exec(30,"gcc","-shared","-fPIC","-Isrc/main/c",source.toString(),"-o",library.toString());
        assertEquals("",run(playback,library.toAbsolutePath().toString()));
    }

    @Test public void incompatibleBackend() throws Exception {
        Path source=Path.of("build/objs/audio_bad.c");
        Path library=Path.of("build/objs/audio_bad"+(TestC.OS.startsWith("Windows") ? ".dll" : ".so"));
        Files.writeString(source,"""
            #include "simple_audio.h"
            static const struct simple_audio_api api={999};
            const struct simple_audio_api *simple_miniaudio_api(void) { return &api; }
            """);
        TestC.exec(30,"gcc","-shared","-fPIC","-Isrc/main/c",source.toString(),"-o",library.toString());
        assertTrue(run(missing,library.toAbsolutePath().toString()).contains("incompatible backend"));
    }
}