Add testsuite to test core libesphttpd functionality

pull/30/head
Jeroen Domburg 8 years ago
parent 3616e6ca2a
commit 6968618432
  1. 1
      html/index.tpl
  2. 9
      html/test/index.html
  3. BIN
      html/test/test.cgi
  4. 205
      html/test/test.js
  5. 2
      libesphttpd
  6. 86
      user/cgi-test.c
  7. 8
      user/cgi-test.h
  8. 5
      user/user_main.c

@ -15,6 +15,7 @@ been loaded <b>%counter%</b> times.
can <a href="flash/index.html">upgrade the firmware</a> of this module.</li>
<li>You can download the raw <a href="flash.bin">contents</a> of the SPI flash rom</li>
<li>Esphttpd now also supports <a href="websocket/index.html">websockets</a>.</li>
<li>Test esphttpd using the built-in <a href="test/">test suite</a></li>
<li>And because I can, here's a link to my <a href="http://spritesmods.com/?f=esphttpd">website</a></ul>
</ul>
</p>

@ -0,0 +1,9 @@
<html><head><title>Webserver test</title></head>
<link rel="stylesheet" type="text/css" href="../wifi/style.css">
<script type="text/javascript" src="../wifi/140medley.min.js"></script>
<script type="text/javascript" src="test.js"></script>
<body>
<div id="main">
<div id="log">Initializing test...</div>
</div>
</body>

Binary file not shown.

@ -0,0 +1,205 @@
/*
Code to test the webserver. This depends on:
- The cat images being available, for concurrent espfs testing
- the test.cgi script available, for generic data mangling tests
This test does a max of 4 requests in parallel. The nonos SDK supports a max of
5 connections; the default libesphttpd setting is 4 sockets at a time. Unfortunately,
the nonos sdk just closes all sockets opened after the available sockets are opened,
instead of queueing them until a socket frees up.
*/
function log(line) {
$("#log").insertAdjacentHTML('beforeend', line+'<br />');
}
//Load an image multiple times in parallel
function testParLdImg(url, ct, doneFn) {
var im=[];
var state={"loaded":0, "count":ct, "doneFn": doneFn, "error":false};
for (var x=0; x<ct; x++) {
im[x]=new Image();
im[x].onload=function(no) {
log("File "+no+" loaded successfully.");
this.loaded++;
if (this.loaded==this.count) this.doneFn(!this.error);
}.bind(state, x);
im[x].onerror=function(no) {
log("Error loading image "+no+"!");
this.loaded++;
this.error++;
if (this.loaded==this.count) this.doneFn(!this.error);
}.bind(state, x);
im[x].src=url+"?"+Math.floor(Math.random()*100000).toString();
}
}
function testDownloadCgi(len, doneFn) {
var xhr=j();
var state={"len":len, "doneFn":doneFn, "ts": Date.now()};
xhr.open("GET", "test.cgi?len="+len+"&nocache="+Math.floor(Math.random()*100000).toString());
xhr.onreadystatechange=function() {
if (xhr.readyState==4 && xhr.status>=200 && xhr.status<300) {
if (xhr.response.length==this.len) {
log("Downloaded "+this.len+" bytes successfully.");
this.doneFn(true);
} else {
log("Downloaded "+xhr.response.length+" bytes successfully, but needed "+this.len+"!");
this.doneFn(false);
}
} else if (xhr.readyState==4) {
log("Failed! Error "+xhr.status);
this.doneFn(false);
}
}.bind(state);
//If the webbrowser enables it, show progress.
if (typeof xhr.onprogress != 'undefined') {
xhr.onprogress=function(e) {
if (Date.now()>this.ts+2000) {
log("..."+Math.floor(e.loaded*100/this.len).toString()+"%");
this.ts=Date.now();
}
}.bind(state);
}
xhr.send();
}
function testUploadCgi(len, doneFn) {
var xhr=j();
var state={"len":len, "doneFn":doneFn, "ts": Date.now()};
var data="";
for (var x=0; x<len; x++) data+="X";
xhr.open("POST", "test.cgi");
xhr.onreadystatechange=function() {
if (xhr.readyState==4 && xhr.status>=200 && xhr.status<300) {
var ulen=parseInt(xhr.responseText);
if (ulen==this.len) {
log("Uploaded "+this.len+" bytes successfully.");
this.doneFn(true);
} else {
log("Webserver received "+ulen+" bytes successfully, but sent "+this.len+"!");
this.doneFn(false);
}
} else if (xhr.readyState==4) {
log("Failed! Error "+xhr.status);
this.doneFn(false);
}
}.bind(state);
//If the webbrowser enables it, show progress.
if (typeof xhr.upload.onprogress != 'undefined') {
xhr.upload.onprogress=function(e) {
if (Date.now()>this.ts+2000) {
log("..."+Math.floor(e.loaded*100/e.total).toString()+"%");
this.ts=Date.now();
}
}.bind(state);
}
//Upload the file
xhr.send(data);
}
function hammerNext(state, xhr) {
if (state.done==state.count) {
state.doneFn(!state.error);
}
if (state.started==state.count) return;
xhr.open("GET", "test.cgi?len="+state.len+"&nocache="+Math.floor(Math.random()*100000).toString());
xhr.onreadystatechange=function(xhr) {
if (xhr.readyState==4 && xhr.status>=200 && xhr.status<300) {
if (xhr.response.length==this.len) {
state.done++;
hammerNext(this, xhr);
} else {
log("Downloaded "+xhr.response.length+" bytes successfully, but needed "+this.len+"!");
state.done++;
hammerNext(this, xhr);
}
} else if (xhr.readyState==4) {
log("Failed! Error "+xhr.status);
state.done++;
hammerNext(this, xhr);
}
}.bind(state, xhr);
//If the webbrowser enables it, show progress.
if (typeof xhr.onprogress != 'undefined') {
xhr.onprogress=function(e) {
if (Date.now()>this.ts+2000) {
log("..."+state.done+"/"+state.count);
this.ts=Date.now();
}
}.bind(state);
}
state.started++;
xhr.send();
}
function testHammer(count, par, len, doneFn) {
var state={"count":count, "started":0, "done":0, "par":par, "len":len, "doneFn":doneFn, "ts": Date.now(), "error":false};
var xhr=[];
for (var i=0; i<par; i++) {
xhr[i]=j();
hammerNext(state, xhr[i]);
}
}
var tstState=0;
var successCnt=0;
function nextTest(lastOk) {
if (tstState!=0) {
if (lastOk) {
log("<b>Success!</b>");
successCnt++;
} else {
log("<b>Test failed!</b>");
}
}
tstState++;
if (tstState==1) {
log("Testing parallel load of espfs files...");
testParLdImg("../cats/kitten-loves-toy.jpg", 3, nextTest);
} else if (tstState==2) {
log("Testing GET request of 32K...");
testDownloadCgi(32*1024, nextTest);
} else if (tstState==3) {
log("Testing GET request of 128K...");
testDownloadCgi(128*1024, nextTest);
} else if (tstState==4) {
log("Testing GET request of 512K...");
testDownloadCgi(512*1024, nextTest);
} else if (tstState==5) {
log("Testing POST request of 512 bytes...");
testUploadCgi(512, nextTest);
} else if (tstState==6) {
log("Testing POST request of 16K bytes...");
testUploadCgi(16*1024, nextTest);
} else if (tstState==7) {
log("Testing POST request of 512K bytes...");
testUploadCgi(512*1024, nextTest);
} else if (tstState==8) {
log("Hammering webserver with 500 requests of size 512...");
testHammer(500, 3, 512, nextTest);
} else if (tstState==9) {
log("Hammering webserver with 500 requests of size 2048...");
testHammer(500, 3, 2048, nextTest);
} else {
log("Tests done! "+successCnt+" out of "+(tstState-1)+" tests were successful.");
}
}
window.onload=function(e) {
log("Starting tests.");
nextTest(false);
}

@ -1 +1 @@
Subproject commit cdc6316b126ea92723743da37945fb92cfa7c924
Subproject commit b1808d27b08bb8915fcfd6d21784fa6069a56c37

@ -0,0 +1,86 @@
/*
Cgi routines as used by the tests in the html/test subdirectory.
*/
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
#include <esp8266.h>
#include "cgi-test.h"
typedef struct {
int len;
int sendPos;
} TestbedState;
int ICACHE_FLASH_ATTR cgiTestbed(HttpdConnData *connData) {
char buff[1024];
int first=0;
int l, x;
TestbedState *state=(TestbedState*)connData->cgiData;
if (connData->conn==NULL) {
//Connection aborted. Clean up.
if (state) free(state);
return HTTPD_CGI_DONE;
}
if (state==NULL) {
//First call
state=malloc(sizeof(TestbedState));
memset(state, 0, sizeof(state));
connData->cgiData=state;
first=1;
}
if (connData->requestType==HTTPD_METHOD_GET) {
if (first) {
httpdStartResponse(connData, 200);
httpdHeader(connData, "content-type", "application/data");
httpdEndHeaders(connData);
l=httpdFindArg(connData->getArgs, "len", buff, sizeof(buff));
state->len=1024;
if (l!=-1) state->len=atoi(buff);
state->sendPos=0;
return HTTPD_CGI_MORE;
} else {
l=sizeof(buff);
if (l>(state->len-state->sendPos)) l=(state->len-state->sendPos);
//Fill with semi-random data
for (x=0; x<l; x++) buff[x]=((x^(state->sendPos>>10))&0x1F)+'0';
httpdSend(connData, buff, l);
state->sendPos+=l;
printf("Test: Uploaded %d/%d bytes\n", state->sendPos, state->len);
if (state->len<=state->sendPos) {
if (state) free(state);
return HTTPD_CGI_DONE;
} else {
return HTTPD_CGI_MORE;
}
}
}
if (connData->requestType==HTTPD_METHOD_POST) {
if (connData->post->len!=connData->post->received) {
//Still receiving data. Ignore this.
printf("Test: got %d/%d bytes\n", connData->post->received, connData->post->len);
return HTTPD_CGI_MORE;
} else {
httpdStartResponse(connData, 200);
httpdHeader(connData, "content-type", "text/plain");
httpdEndHeaders(connData);
l=sprintf(buff, "%d", connData->post->received);
httpdSend(connData, buff, l);
return HTTPD_CGI_DONE;
}
}
return HTTPD_CGI_DONE;
}

@ -0,0 +1,8 @@
#ifndef CGI_TEST_H
#define CGI_TEST_H
#include "httpd.h"
int cgiTestbed(HttpdConnData *connData);
#endif

@ -26,6 +26,7 @@ some pictures of cats.
#include "captdns.h"
#include "webpages-espfs.h"
#include "cgiwebsocket.h"
#include "cgi-test.h"
//The example can print out the heap use every 3 seconds. You can use this to catch memory leaks.
//#define SHOW_HEAP_USE
@ -147,6 +148,10 @@ HttpdBuiltInUrl builtInUrls[]={
{"/websocket/ws.cgi", cgiWebsocket, myWebsocketConnect},
{"/websocket/echo.cgi", cgiWebsocket, myEchoWebsocketConnect},
{"/test", cgiRedirect, "/test/index.html"},
{"/test/", cgiRedirect, "/test/index.html"},
{"/test/test.cgi", cgiTestbed, NULL},
{"*", cgiEspFsHook, NULL}, //Catch-all cgi function for the filesystem
{NULL, NULL, NULL}
};

Loading…
Cancel
Save