Files
ldc/druntime/src/compiler/dmd/cover.d
2009-06-02 17:43:06 +01:00

464 lines
11 KiB
D

/**
* Implementation of code coverage analyzer.
*
* Copyright: Copyright Digital Mars 2000 - 2009.
* License: <a href="http://www.boost.org/LICENSE_1_0.txt>Boost License 1.0</a>.
* Authors: Walter Bright, Sean Kelly
*
* Copyright Digital Mars 2000 - 2009.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*/
module rt.cover;
private
{
version( Windows )
import core.sys.windows.windows;
else version( Posix )
{
import core.sys.posix.fcntl;
import core.sys.posix.unistd;
}
import core.bitop;
import core.stdc.stdio;
import rt.util.utf;
struct BitArray
{
size_t len;
uint* ptr;
bool opIndex( size_t i )
in
{
assert( i < len );
}
body
{
return cast(bool) bt( ptr, i );
}
}
struct Cover
{
string filename;
BitArray valid;
uint[] data;
}
__gshared
{
Cover[] gdata;
string srcpath;
string dstpath;
bool merge;
}
}
/**
* Set path to where source files are located.
*
* Params:
* pathname = The new path name.
*/
extern (C) void dmd_coverSourcePath( string pathname )
{
srcpath = pathname;
}
/**
* Set path to where listing files are to be written.
*
* Params:
* pathname = The new path name.
*/
extern (C) void dmd_coverDestPath( string pathname )
{
dstpath = pathname;
}
/**
* Set merge mode.
*
* Params:
* flag = true means new data is summed with existing data in the listing
* file; false means a new listing file is always created.
*/
extern (C) void dmd_coverSetMerge( bool flag )
{
merge = flag;
}
/**
* The coverage callback.
*
* Params:
* filename = The name of the coverage file.
* valid = ???
* data = ???
*/
extern (C) void _d_cover_register( string filename, BitArray valid, uint[] data )
{
Cover c;
c.filename = filename;
c.valid = valid;
c.data = data;
gdata ~= c;
}
static ~this()
{
const NUMLINES = 16384 - 1;
const NUMCHARS = 16384 * 16 - 1;
char[] srcbuf = new char[NUMCHARS];
char[][] srclines = new char[][NUMLINES];
char[] lstbuf = new char[NUMCHARS];
char[][] lstlines = new char[][NUMLINES];
foreach( Cover c; gdata )
{
if( !readFile( appendFN( srcpath, c.filename ), srcbuf ) )
continue;
splitLines( srcbuf, srclines );
if( merge )
{
if( !readFile( addExt( baseName( c.filename ), "lst" ), lstbuf ) )
break;
splitLines( lstbuf, lstlines );
for( size_t i = 0; i < lstlines.length; ++i )
{
if( i >= c.data.length )
break;
int count = 0;
foreach( char c2; lstlines[i] )
{
switch( c2 )
{
case ' ':
continue;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
count = count * 10 + c2 - '0';
continue;
default:
break;
}
}
c.data[i] += count;
}
}
FILE* flst = fopen( (addExt( baseName( c.filename ), "lst\0" )).ptr, "wb" );
if( !flst )
continue; //throw new Exception( "Error opening file for write: " ~ lstfn );
uint nno;
uint nyes;
for( int i = 0; i < c.data.length; i++ )
{
if( i < srclines.length )
{
uint n = c.data[i];
char[] line = srclines[i];
line = expandTabs( line );
if( n == 0 )
{
if( c.valid[i] )
{
nno++;
fprintf( flst, "0000000|%.*s\n", line );
}
else
{
fprintf( flst, " |%.*s\n", line );
}
}
else
{
nyes++;
fprintf( flst, "%7u|%.*s\n", n, line );
}
}
}
if( nyes + nno ) // no divide by 0 bugs
{
fprintf( flst, "%.*s is %d%% covered\n", c.filename, ( nyes * 100 ) / ( nyes + nno ) );
}
fclose( flst );
}
}
string appendFN( string path, string name )
{
version( Windows )
const char sep = '\\';
else
const char sep = '/';
auto dest = path;
if( dest && dest[$ - 1] != sep )
dest ~= sep;
dest ~= name;
return dest;
}
string baseName( string name, string ext = null )
{
auto i = name.length;
for( ; i > 0; --i )
{
version( Windows )
{
if( name[i - 1] == ':' || name[i - 1] == '\\' )
break;
}
else version( Posix )
{
if( name[i - 1] == '/' )
break;
}
}
return chomp( name[i .. $], ext ? ext : "" );
}
string getExt( string name )
{
auto i = name.length;
while( i > 0 )
{
if( name[i - 1] == '.' )
return name[i .. $];
--i;
version( Windows )
{
if( name[i] == ':' || name[i] == '\\' )
break;
}
else version( Posix )
{
if( name[i] == '/' )
break;
}
}
return null;
}
string addExt( string name, string ext )
{
auto existing = getExt( name );
if( existing.length == 0 )
{
if( name.length && name[$ - 1] == '.' )
name ~= ext;
else
name = name ~ "." ~ ext;
}
else
{
name = name[0 .. $ - existing.length] ~ ext;
}
return name;
}
string chomp( string str, string delim = null )
{
if( delim is null )
{
auto len = str.length;
if( len )
{
auto c = str[len - 1];
if( c == '\r' )
--len;
else if( c == '\n' && str[--len - 1] == '\r' )
--len;
}
return str[0 .. len];
}
else if( str.length >= delim.length )
{
if( str[$ - delim.length .. $] == delim )
return str[0 .. $ - delim.length];
}
return str;
}
bool readFile( string name, inout char[] buf )
{
version( Windows )
{
auto wnamez = toUTF16z( name );
HANDLE file = CreateFileW( wnamez,
GENERIC_READ,
FILE_SHARE_READ,
null,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
cast(HANDLE) null );
delete wnamez;
if( file == INVALID_HANDLE_VALUE )
return false;
scope( exit ) CloseHandle( file );
DWORD num = 0;
DWORD pos = 0;
buf.length = 4096;
while( true )
{
if( !ReadFile( file, &buf[pos], cast(DWORD)( buf.length - pos ), &num, null ) )
return false;
if( !num )
break;
pos += num;
buf.length = pos * 2;
}
buf.length = pos;
return true;
}
else version( Posix )
{
char[] namez = new char[name.length + 1];
namez[0 .. name.length] = name;
namez[$ - 1] = 0;
int file = open( namez.ptr, O_RDONLY );
delete namez;
if( file == -1 )
return false;
scope( exit ) close( file );
int num = 0;
uint pos = 0;
buf.length = 4096;
while( true )
{
num = read( file, &buf[pos], cast(uint)( buf.length - pos ) );
if( num == -1 )
return false;
if( !num )
break;
pos += num;
buf.length = pos * 2;
}
buf.length = pos;
return true;
}
}
void splitLines( char[] buf, inout char[][] lines )
{
size_t beg = 0,
pos = 0;
lines.length = 0;
for( ; pos < buf.length; ++pos )
{
char c = buf[pos];
switch( buf[pos] )
{
case '\r':
case '\n':
lines ~= buf[beg .. pos];
beg = pos + 1;
if( buf[pos] == '\r' && pos < buf.length - 1 && buf[pos + 1] == '\n' )
++pos, ++beg;
default:
continue;
}
}
if( beg != pos )
{
lines ~= buf[beg .. pos];
}
}
char[] expandTabs( char[] str, int tabsize = 8 )
{
const dchar LS = '\u2028'; // UTF line separator
const dchar PS = '\u2029'; // UTF paragraph separator
bool changes = false;
char[] result = str;
int column;
int nspaces;
foreach( size_t i, dchar c; str )
{
switch( c )
{
case '\t':
nspaces = tabsize - (column % tabsize);
if( !changes )
{
changes = true;
result = null;
result.length = str.length + nspaces - 1;
result.length = i + nspaces;
result[0 .. i] = str[0 .. i];
result[i .. i + nspaces] = ' ';
}
else
{ int j = result.length;
result.length = j + nspaces;
result[j .. j + nspaces] = ' ';
}
column += nspaces;
break;
case '\r':
case '\n':
case PS:
case LS:
column = 0;
goto L1;
default:
column++;
L1:
if (changes)
{
if (c <= 0x7F)
result ~= c;
else
encode(result, c);
}
break;
}
}
return result;
}