open-source-search-engine/QAClient.cpp

712 lines
17 KiB
C++
Raw Normal View History

2013-08-03 00:12:24 +04:00
#include "gb-include.h"
#include "QAClient.h"
#include "Log.h"
#include "HttpServer.h"
#include "Process.h"
#include "Diff.h"
#define MAX_QA_SOCKETS 300
#define MAX_TEST_URLS 2000
static void doQA ( int fd , void *state ) ;
static void nextTestWrapper(void *state);
static char *s_parserUrls[] =
{"http://reddit.com/",
"http://www.searchengineshowdown.com/cse/search-state-libraries/"
};
static char *s_queries[] =
{"q=the&n=10",
"q=search&n=100"
};
QAClient::QAClient(){
m_numTests = 0;
m_curTest = NULL;
m_pbuf = NULL;
m_pbufPtr = NULL;
m_qbuf = NULL;
m_qbufPtr = NULL;
}
QAClient::~QAClient(){
if (m_pbuf){
mfree(m_pbuf, m_pbufSize, "qaparse");
m_pbuf = NULL;
}
if (m_pbufPtr){
mfree(m_pbufPtr, m_pbufPtrSize, "qaparse");
m_pbufPtr = NULL;
}
if (m_qbuf){
mfree(m_qbuf, m_qbufSize, "qaquery");
m_qbuf = NULL;
}
if (m_qbufPtr){
mfree(m_qbufPtr, m_qbufPtrSize, "qaquery");
m_qbufPtr = NULL;
}
}
bool QAClient::init(char *s1, char * s2, char *pfile, char *qfile){
// get host
Host *h = g_hostdb.getHost ( 0 );
if ( ! h ) return log("build: inject: Hostid %i is invalid.",
0)-1;
g_conf.m_save = false;
g_conf.m_readOnlyMode = true;
g_conf.m_useThreads = false;
g_conf.m_useQuickpoll = false;
// download 1 page from each server simultaneously
//g_conf.m_httpMaxDownloadSockets = 2;
g_conf.m_httpMaxSockets = 2;
if ( ! hashinit() ) {
log("db: Failed to init hashtable." ); return 1; }
// . hashinit() calls srand() w/ a fixed number
// . let's mix it up again
srand ( time(NULL) );
// set up the loop
if ( ! g_loop.init() ) return log("build: inject: Loop init "
"failed.")-1;
//g_process.init();
//if ( ! g_dns.init( h->m_dnsClientPort ) ) {
// log("db: Dns distributed client init failed." ); return 1; }
// set up http server for client only
if ( ! g_httpServer.init( 0, 0, NULL))
return log("db: HttpServer init failed.");
// Set up server urls
if (s1) m_server1.set(s1, gbstrlen(s1), false);
if (s2) m_server2.set(s2, gbstrlen(s2), false);
if (!s1){
// load servers from hosts file
char buf[MAX_URL_LEN];
snprintf(buf, MAX_URL_LEN, "http://%s:%hd/",
iptoa(h->m_ip), h->m_httpPort);
m_server1.set(buf, gbstrlen(buf), false);
// tmp server: httpPort + 1
snprintf(buf, MAX_URL_LEN, "http://%s:%hd/",
iptoa(h->m_ip), h->m_httpPort+1);
m_server2.set(buf, gbstrlen(buf), false);
}
// Initialize tests
m_curTest = NULL;
m_nextTest = 0;
if (!pfile){
m_parserUrls = s_parserUrls;
m_numParserUrls = sizeof(s_parserUrls)/sizeof(char*);
}
else {
// load urls from file, and put them in an allocated buffer
int fd = open(pfile, O_RDONLY);
if (fd < 0 ) {
fprintf(stderr, "error opening %s\n", pfile);
return false;
}
2014-11-11 01:45:11 +03:00
int32_t len = lseek(fd, 0, SEEK_END);
2013-08-03 00:12:24 +04:00
if (len < 0){
fprintf(stderr, "error seeking %s\n", pfile);
close(fd);
return false;
}
char *buf = (char*)mmalloc(len+1, "qaparse");
if (!buf){
2014-11-11 01:45:11 +03:00
fprintf(stderr, "can't alloc %"INT32" bytes for %s\n",
2013-08-03 00:12:24 +04:00
len+1, pfile);
close(fd);
return false;
}
buf[len] = '\0';
lseek(fd, 0, SEEK_SET);
2014-11-11 01:45:11 +03:00
int32_t n = read(fd, buf, len);
2013-08-03 00:12:24 +04:00
if (n && n != len){
fprintf(stderr, "error reading %s: "
2014-11-11 01:45:11 +03:00
"expected %"INT32" bytes got %"INT32".\n"
2013-08-03 00:12:24 +04:00
"(errno=%s)\n",
pfile, len, n, strerror(errno));
return false;
}
// allocate a list of pointers to each url in the file.
2014-11-11 01:45:11 +03:00
int32_t size = MAX_TEST_URLS*sizeof(char**);
2013-08-03 00:12:24 +04:00
char **ptrs = (char**) mmalloc(size, "qaparse");
if (!buf){
2014-11-11 01:45:11 +03:00
fprintf(stderr, "can't alloc %"INT32" bytes for %s\n",
2013-08-03 00:12:24 +04:00
size, pfile);
close(fd);
return false;
}
// fill the pointer list and null-terminate each url
2014-11-11 01:45:11 +03:00
int32_t urls = 0;
2013-08-03 00:12:24 +04:00
for (char *p=buf, *q=buf,*pend=buf+len ;
p<pend && urls < MAX_TEST_URLS; p++){
if (*p && *p!='\n' && *p!='\r' && *p!='\t' && *p!=' ')
continue;
*p = '\0';
// skip whitespace
if (p-q <= 0) { q=p+1; continue; }
ptrs[urls++] = q;
q = p+1;
}
m_pbuf = buf;
m_pbufSize = len+1;
m_pbufPtr = ptrs;
m_pbufPtrSize = size;
m_parserUrls = ptrs;
m_numParserUrls = urls;
close(fd);
}
if (!qfile){
m_queries = s_queries;
m_numQueries = sizeof(s_queries)/sizeof(char*);
}
else{
// load urls from file, and put them in an allocated buffer
int fd = open(qfile, O_RDONLY);
if (fd < 0 ) {
fprintf(stderr, "error opening %s\n", qfile);
return false;
}
2014-11-11 01:45:11 +03:00
int32_t len = lseek(fd, 0, SEEK_END);
2013-08-03 00:12:24 +04:00
if (len < 0){
fprintf(stderr, "error seeking %s\n", qfile);
close(fd);
return false;
}
char *buf = (char*)mmalloc(len+1, "qaquery");
if (!buf){
2014-11-11 01:45:11 +03:00
fprintf(stderr, "can't alloc %"INT32" bytes for %s\n",
2013-08-03 00:12:24 +04:00
len+1, qfile);
close(fd);
return false;
}
buf[len] = '\0';
lseek(fd, 0, SEEK_SET);
2014-11-11 01:45:11 +03:00
int32_t n = read(fd, buf, len);
2013-08-03 00:12:24 +04:00
if (n && n != len){
fprintf(stderr, "error reading %s: "
2014-11-11 01:45:11 +03:00
"expected %"INT32" bytes got %"INT32".\n"
2013-08-03 00:12:24 +04:00
"(errno=%s)\n",
pfile, len, n, strerror(errno));
return false;
}
// allocate a list of pointers to each url in the file.
2014-11-11 01:45:11 +03:00
int32_t size = MAX_TEST_URLS*sizeof(char**);
2013-08-03 00:12:24 +04:00
char **ptrs = (char**) mmalloc(size, "qaquery");
if (!buf){
2014-11-11 01:45:11 +03:00
fprintf(stderr, "can't alloc %"INT32" bytes for %s\n",
2013-08-03 00:12:24 +04:00
size, qfile);
close(fd);
return false;
}
// fill the pointer list and null-terminate each url
2014-11-11 01:45:11 +03:00
int32_t urls = 0;
2013-08-03 00:12:24 +04:00
for (char *p=buf, *q=buf,*pend=buf+len ;
p<pend && urls < MAX_TEST_URLS; p++){
if (*p && *p!='\n' && *p!='\r' && *p!='\t' && *p!=' ')
continue;
*p = '\0';
// skip whitespace
if (p-q <= 0) { q=p+1; continue; }
ptrs[urls++] = q;
q = p+1;
}
m_qbuf = buf;
m_qbufSize = len+1;
m_qbufPtr = ptrs;
m_qbufPtrSize = size;
m_queries = ptrs;
m_numQueries = urls;
close(fd);
}
m_numTests = m_numParserUrls + m_numQueries;
2014-11-11 01:45:11 +03:00
// int32_t numTests = 2;
// for (int32_t i=0;i<numTests && i < MAX_QA_TESTS ; i++){
2013-08-03 00:12:24 +04:00
// char u1[MAX_URL_LEN];
// snprintf(u1,MAX_URL_LEN,
// "%smaster/parser?username=qa&pwd=&c=main&cast=0&u=%s",
// m_server1.getUrl(), s_testUrls[i]);
// u1[MAX_URL_LEN-1] = '\0';
// char u2[MAX_URL_LEN];
// snprintf(u2, MAX_URL_LEN,
// "%smaster/parser?username=qa&pwd=&c=main&cast=0&u=%s",
// m_server2.getUrl(), s_testUrls[i]);
// u2[MAX_URL_LEN-1] = '\0';
// m_tests[i].set(u1,u2, this, nextTestWrapper);
// m_numTests++;
// }
return true;
}
bool QAClient::runTests(){
// register sleep callback to get started
if ( ! g_loop.registerSleepCallback(1, this, doQA) )
return log("build: inject: Loop init failed.")-1;
// run the loop
if ( ! g_loop.runLoop() ) return log("build: inject: Loop "
"run failed.")-1;
return true;
}
static void doQA( int fd , void *state){
g_loop.unregisterSleepCallback( state, doQA);
log(LOG_INIT, "qa: Starting tests");
QAClient *qa = (QAClient*)state;
qa->runNextTest();
}
void nextTestWrapper(void *state){
QAClient *qa = (QAClient*)state;
qa->runNextTest();
}
void QAClient::runNextTest(){
// Free last test
if (m_curTest){
delete m_curTest;
mdelete(m_curTest, m_curTestSize, "qaclient");
m_curTest = NULL;
m_curTestSize = 0;
}
if (m_nextTest >= m_numTests) {
log(LOG_INIT,"qa: Done with tests");
exit(0);
}
// Moved test initialization here since we don't need them all
// in memory at once
2014-11-11 01:45:11 +03:00
int32_t i = m_nextTest;
2013-08-03 00:12:24 +04:00
// PageParser tests
if (i < m_numParserUrls){
// encode the url for including as a parm
char testUrl[MAX_URL_LEN];
urlEncode(testUrl,MAX_URL_LEN,
m_parserUrls[i],gbstrlen(m_parserUrls[i]));
testUrl[MAX_URL_LEN-1] = '\0';
// construct the request for server 1
char u1[MAX_URL_LEN];
snprintf(u1,MAX_URL_LEN,
"%smaster/parser?username=qa&old=1&c=main&cast=0&u=%s",
m_server1.getUrl(), testUrl);
u1[MAX_URL_LEN-1] = '\0';
// construct the request for server 2
char u2[MAX_URL_LEN];
snprintf(u2, MAX_URL_LEN,
"%smaster/parser?username=qa&old=1&c=main&cast=0&u=%s",
m_server2.getUrl(), testUrl);
u2[MAX_URL_LEN-1] = '\0';
QADiffTest *t;
try {
t = new QADiffTest();
}
catch(...) {
log("qa: Unable to allocate qa test!");
exit(0);
}
m_curTestSize = sizeof(*t);
mnew(t, m_curTestSize, "qaclient");
char desc[256];
2014-11-11 01:45:11 +03:00
sprintf(desc, "parser_%"INT32"", i);
2013-08-03 00:12:24 +04:00
t->set(desc,u1,u2, this, nextTestWrapper);
m_curTest = t;
}
i -= m_numParserUrls;
// query diff tests
if (i>=0 && i < m_numQueries){
char u1[MAX_URL_LEN];
snprintf(u1,MAX_URL_LEN,
"%ssearch?username=qa&pwd=&c=main&raw=9&%s",
m_server1.getUrl(), m_queries[i]);
u1[MAX_URL_LEN-1] = '\0';
char u2[MAX_URL_LEN];
snprintf(u2, MAX_URL_LEN,
"%ssearch?username=qa&pwd=&c=main&raw=9&%s",
m_server2.getUrl(), m_queries[i]);
u2[MAX_URL_LEN-1] = '\0';
QADiffTest *t;
try {
t = new QADiffTest();
}
catch(...) {
log("qa: Unable to allocate qa test!");
exit(0);
}
m_curTestSize = sizeof(*t);
mnew(t, m_curTestSize, "qaclient");
char desc[256];
2014-11-11 01:45:11 +03:00
sprintf(desc, "query_%"INT32"", i);
2013-08-03 00:12:24 +04:00
t->set(desc,u1,u2, this, nextTestWrapper);
m_curTest = t;
}
m_nextTest++;
m_curTest->startTest();
}
void QATest::reset(){
}
QATest::~QATest(){
reset();
}
//QADiffTest::~QADiffTest(){
// reset();
//}
bool QADiffTest::set(char *desc,
char *u1,
char *u2,
void *state,
qatest_callback_t callback){
reset();
m_doc1.m_url.set(u1, gbstrlen(u1), false);
m_doc2.m_url.set(u2, gbstrlen(u2), false);
m_state = state;
m_callback = callback;
if (desc) strncpy(m_desc, desc, 255);
m_desc[255] = '\0';
return true;
}
static void gotDiffTestDocWrapper ( void *state , HttpDoc *doc) ;
void QADiffTest::startTest(){
log(LOG_INIT, "qa: starting test: %s", m_desc);
m_doc1.get(this, gotDiffTestDocWrapper);
m_doc2.get(this, gotDiffTestDocWrapper);
}
void gotDiffTestDocWrapper ( void *state , HttpDoc *doc) {
QADiffTest *t = (QADiffTest*)state;
if (t->m_doc1.m_done && t->m_doc2.m_done){
t->processResults();
}
}
// encapsulate doc-fetching
HttpDoc::HttpDoc(){
m_ownBuf = false;
m_buf = NULL;
}
HttpDoc::~HttpDoc(){
reset();
}
void HttpDoc::reset() {
m_url.reset();
m_done = false;
m_errno = 0;
m_startTime = 0;
m_elapsed = 0;
m_content = NULL;
m_contentLen = 0;
m_httpStatus = 0;
if (m_buf && m_ownBuf){
mfree(m_buf, m_bufSize, "httpdoc");
}
m_buf = NULL;
m_bufSize = 0;
m_ownBuf = false;
m_callback = NULL;
m_state = NULL;
};
static void gotHttpDoc ( void *state , TcpSocket *ts ) ;
void HttpDoc::get(void *state, httpdoc_callback_t callback){
m_callback = callback;
m_state = state;
//log(LOG_INIT, "qa: HttpDoc: fetching %s", m_url.getUrl());
m_startTime = gettimeofdayInMilliseconds();
if ( g_httpServer.getDoc ( &m_url,
0,
-1,
0,
this,
gotHttpDoc,
30*1000,
0, // proxyip
0, // proxyport
10000000, // r->m_maxTextDocLen ,
10000000, // r->m_maxOtherDocLen ,
NULL ,
true ) ) {
// on error g_errno should be set, call this now
//if (callback) callback ( this , NULL );
gotHttpDoc(this, NULL);
}
}
// Fill in HttpDoc data structure with contents from server
void gotHttpDoc ( void *state , TcpSocket *ts ) {
HttpDoc *doc = (HttpDoc*) state;
doc->m_done = true;
2014-10-30 22:36:39 +03:00
int64_t now = gettimeofdayInMilliseconds();
2013-08-03 00:12:24 +04:00
doc->m_elapsed = now - doc->m_startTime;
if (g_errno){
doc->m_errno = g_errno;
g_errno = 0;
if (doc->m_callback) doc->m_callback(doc->m_state, doc);
return;
}
char *reply = ts->m_readBuf;
2014-11-11 01:45:11 +03:00
int32_t replyLen = ts->m_readOffset;
//int32_t replySize = ts->m_readBufSize;
2013-08-03 00:12:24 +04:00
HttpMime mime;
Url url;
mime.set ( reply , replyLen , &url ) ;
2014-11-11 01:45:11 +03:00
int32_t mimeLen = mime.getMimeLen();
2013-08-03 00:12:24 +04:00
doc->m_content = reply + mimeLen;
doc->m_contentLen = replyLen - mimeLen;
doc->m_httpStatus = mime.getHttpStatus();
doc->m_buf = ts->m_readBuf;
doc->m_bufSize = ts->m_readBufSize;
doc->m_ownBuf = true;
char *http_cs = mime.getCharset();
2014-11-11 01:45:11 +03:00
int32_t cslen = mime.getCharsetLen();
2013-08-03 00:12:24 +04:00
//logf(LOG_INFO, "qa: http: %s",http_cs);
doc->m_charset = get_iana_charset(http_cs, cslen);
// . nullify the buf in the socket so it doesn't get freed,
ts->m_readBuf = NULL;
if (doc->m_callback) doc->m_callback(doc->m_state, doc);
}
void QADiffTest::processResults(){
//log(LOG_INIT,"qa: QADiffTest: Processing");
printf("vvv BEGIN TEST %s vvv\n", m_desc);
printf("doc1=%s\ndoc2=%s\n",
m_doc1.m_url.getUrl(),
m_doc2.m_url.getUrl());
2014-11-11 01:45:11 +03:00
printf("status=(%d,%d) elapsed=(%lldms,%lldms) size=(%"INT32",%"INT32")\n",
2013-08-03 00:12:24 +04:00
m_doc1.m_httpStatus, m_doc2.m_httpStatus,
m_doc1.m_elapsed, m_doc2.m_elapsed,
m_doc1.m_contentLen, m_doc2.m_contentLen);
if (m_doc1.m_httpStatus == m_doc2.m_httpStatus){
printf("\nvvv BEGIN DIFF %s vvv\n", m_desc);
fflush(stdout);
//fileDiff();
xmlDiff();
printf("^^^ END DIFF ^^^\n\n");
}
else{
printf("***ERROR: http status does not match!\n");
}
printf("^^^ END TEST %s ^^^\n\n", m_desc);
fflush(stdout);
if (m_callback) m_callback(m_state);
}
// diff the contents of m_doc1 and m_doc2 with unix diff
void QADiffTest::fileDiff() {
// write page contents to files for diff
ssize_t nw;
int fd;
char filename1[256];
char filename2[256];
int pid = getpid();
char cmd[550];
int status = 0;
// Only diff if we actually got content from both servers
if ((m_doc1.m_httpStatus != 200) ||
(m_doc2.m_httpStatus != 200)) {
goto abort;
}
//sprintf(filename1, "tmp/qadiff.%d.1", pid);
//sprintf(filename2, "tmp/qadiff.%d.2", pid);
sprintf(filename1, "/tmp/qa.%s.%d.1", m_desc,pid);
sprintf(filename2, "/tmp/qa.%s.%d.2", m_desc,pid);
fd = open(filename1, O_RDWR|O_CREAT|O_TRUNC, 00666);
if (fd < 0) {
log(LOG_WARN,
"qa: QADiffTest: unable to open \"%s\" for writing: %s",
filename1, strerror(errno));
goto abort;
}
nw = write(fd, m_doc1.m_content, m_doc1.m_contentLen);
if (nw < 0) {
log(LOG_WARN,
"qa: QADiffTest: unable write to \"%s\": %s",
filename1, strerror(errno));
close(fd);
goto abort;
}
write(fd, "\n", 1);
close(fd);
fd = open(filename2, O_RDWR|O_CREAT|O_TRUNC, 00666);
if (fd < 0) {
log(LOG_WARN,
"qa: QADiffTest: unable to open \"%s\" for writing: %s",
filename2, strerror(errno));
goto abort;
}
nw = write(fd, m_doc2.m_content, m_doc2.m_contentLen);
if (nw < 0) {
log(LOG_WARN,
"qa: QADiffTest: unable write to \"%s\": %s",
filename2, strerror(errno));
close(fd);
goto abort;
}
write(fd, "\n", 1);
close(fd);
sprintf(cmd, "diff '%s' '%s' 2>&1", filename1, filename2);
status = system(cmd);
if (status == 0) {
unlink(filename1);
unlink(filename2);
}
return;
abort:
unlink(filename1);
unlink(filename2);
return;
}
// diff the contents of m_doc1 and m_doc2
void QADiffTest::xmlDiff() {
Xml xml1;
Xml xml2;
xml1.set(m_doc1.m_charset,
m_doc1.m_content,
m_doc1.m_contentLen,
false, 0, false,
TITLEREC_CURRENT_VERSION);
xml2.set(m_doc2.m_charset,
m_doc2.m_content,
m_doc2.m_contentLen,
false, 0, false,
TITLEREC_CURRENT_VERSION);
2014-11-11 01:45:11 +03:00
log(LOG_INIT, "qa: doc1: charset=%d len=%"INT32" "
"xml1: charset=%d len=%"INT32" nodes=%"INT32"\n",
2013-08-03 00:12:24 +04:00
m_doc1.m_charset, m_doc1.m_contentLen,
xml1.getCharset(),xml1.getContentLen() , xml1.getNumNodes() );
2014-11-11 01:45:11 +03:00
log(LOG_INIT, "qa: doc2: charset=%d len=%"INT32" "
"xml2: charset=%d len=%"INT32" nodes=%"INT32"\n",
2013-08-03 00:12:24 +04:00
m_doc2.m_charset, m_doc2.m_contentLen,
xml2.getCharset(),xml2.getContentLen(), xml2.getNumNodes() );
DiffOpt opt;
opt.m_debug = 0;
opt.m_context = 3;
printXmlDiff(&xml1,&xml2, &opt);
#if 0
2014-11-11 01:45:11 +03:00
int32_t seq1 [4096];
int32_t seq2 [4096];
int32_t seqLen[4096];
2013-08-03 00:12:24 +04:00
2014-11-11 01:45:11 +03:00
int32_t lcsLen = int32_testCommonSubsequence(seq1, seq2, seqLen,4096,
2013-08-03 00:12:24 +04:00
&xml1, &xml2);
2014-11-11 01:45:11 +03:00
printf("lcs length: %"INT32"\n", lcsLen);
2013-08-03 00:12:24 +04:00
lcsLen = lcsXml(seq1, seq2, seqLen,4096,
&xml1, &xml2, opt);
2014-11-11 01:45:11 +03:00
printf("lcs length: %"INT32"\n", lcsLen);
2013-08-03 00:12:24 +04:00
2014-11-11 01:45:11 +03:00
int32_t start = -1;
2013-08-03 00:12:24 +04:00
printf("[ ");
2014-11-11 01:45:11 +03:00
for (int32_t i=0;i<lcsLen;i++){
int32_t n1 = lcs1[i];
int32_t n2 = lcs2[i];
printf("(%"INT32", %"INT32")", n1, n2);
2013-08-03 00:12:24 +04:00
if (i < lcsLen-1) printf(", ");
continue;
if (start < 0) {
2014-11-11 01:45:11 +03:00
printf("%"INT32"", n);
2013-08-03 00:12:24 +04:00
start = n;
continue;
}
// only print start and end of ranges
if (start == n-1){
start = n;
// more in the sequence
if (i < lcsLen-1 && lcs1[i+1] == n+1 ) continue;
// end of sequence
2014-11-11 01:45:11 +03:00
printf("-%"INT32"", lcs1[i]);
2013-08-03 00:12:24 +04:00
continue;
}
// disconnected node
2014-11-11 01:45:11 +03:00
printf(", %"INT32"", n);
2013-08-03 00:12:24 +04:00
start = n;
}
printf(" ]\n");
// Print Diff
2014-11-11 01:45:11 +03:00
int32_t n1 = xml1.getNumNodes();
int32_t n2 = xml2.getNumNodes();
int32_t iLcs = -1;
int32_t i = 0;
int32_t j = 0;
2013-08-03 00:12:24 +04:00
for (iLcs=0;i<lcsLen;iLcs++){
2014-11-11 01:45:11 +03:00
int32_t a = seq1[iLcs];
int32_t b = seq2[iLcs];
int32_t len = seqLen[iLcs];
2013-08-03 00:12:24 +04:00
// Deletes
while (i < a){
2014-11-11 01:45:11 +03:00
printf("- %"INT32"\n", i++);
2013-08-03 00:12:24 +04:00
}
// Adds
while (j < b){
2014-11-11 01:45:11 +03:00
printf("+ %"INT32"\n", j++);
2013-08-03 00:12:24 +04:00
}
// Same
if (i == a) i+=len;
if (j == b) j+=len;
}
// Deletes
while (i < n1){
2014-11-11 01:45:11 +03:00
printf("- %"INT32"\n", i++);
2013-08-03 00:12:24 +04:00
}
// Adds
while (j < n2){
2014-11-11 01:45:11 +03:00
printf("+ %"INT32"\n", j++);
2013-08-03 00:12:24 +04:00
}
#endif
}